Commit ·
c4d144f
1
Parent(s): 0b40c02
auto
Browse files- commit.bat +30 -0
- extensions/Stable-Diffusion-Webui-Civitai-Helper/about_version2.md +0 -30
- extensions/Stable-Diffusion-Webui-Civitai-Helper/javascript/civitai_helper.js +694 -288
- extensions/Stable-Diffusion-Webui-Civitai-Helper/scripts/__pycache__/civitai_helper.cpython-310.pyc +0 -0
- extensions/Stable-Diffusion-Webui-Civitai-Helper/scripts/ch_lib/__pycache__/__init__.cpython-310.pyc +0 -0
- extensions/Stable-Diffusion-Webui-Civitai-Helper/scripts/ch_lib/__pycache__/civitai.cpython-310.pyc +0 -0
- extensions/Stable-Diffusion-Webui-Civitai-Helper/scripts/ch_lib/__pycache__/downloader.cpython-310.pyc +0 -0
- extensions/Stable-Diffusion-Webui-Civitai-Helper/scripts/ch_lib/__pycache__/js_action_civitai.cpython-310.pyc +0 -0
- extensions/Stable-Diffusion-Webui-Civitai-Helper/scripts/ch_lib/__pycache__/model.cpython-310.pyc +0 -0
- extensions/Stable-Diffusion-Webui-Civitai-Helper/scripts/ch_lib/__pycache__/model_action_civitai.cpython-310.pyc +0 -0
- extensions/Stable-Diffusion-Webui-Civitai-Helper/scripts/ch_lib/__pycache__/msg_handler.cpython-310.pyc +0 -0
- extensions/Stable-Diffusion-Webui-Civitai-Helper/scripts/ch_lib/__pycache__/setting.cpython-310.pyc +0 -0
- extensions/Stable-Diffusion-Webui-Civitai-Helper/scripts/ch_lib/__pycache__/util.cpython-310.pyc +0 -0
- extensions/Stable-Diffusion-Webui-Civitai-Helper/scripts/ch_lib/civitai.py +534 -425
- extensions/Stable-Diffusion-Webui-Civitai-Helper/scripts/ch_lib/downloader.py +334 -91
- extensions/Stable-Diffusion-Webui-Civitai-Helper/scripts/ch_lib/duplicate_check.py +392 -0
- extensions/Stable-Diffusion-Webui-Civitai-Helper/scripts/ch_lib/js_action_civitai.py +218 -159
- extensions/Stable-Diffusion-Webui-Civitai-Helper/scripts/ch_lib/model.py +603 -94
- extensions/Stable-Diffusion-Webui-Civitai-Helper/scripts/ch_lib/model_action_civitai.py +698 -381
- extensions/Stable-Diffusion-Webui-Civitai-Helper/scripts/ch_lib/msg_handler.py +44 -34
- extensions/Stable-Diffusion-Webui-Civitai-Helper/scripts/ch_lib/setting.py +0 -103
- extensions/Stable-Diffusion-Webui-Civitai-Helper/scripts/ch_lib/templates.py +94 -0
- extensions/Stable-Diffusion-Webui-Civitai-Helper/scripts/ch_lib/util.py +451 -66
- extensions/Stable-Diffusion-Webui-Civitai-Helper/scripts/civitai_helper.py +255 -191
- extensions/Stable-Diffusion-Webui-Civitai-Helper/scripts/sections.py +693 -0
- extensions/Stable-Diffusion-Webui-Civitai-Helper/style.css +108 -1
- extensions/sd-webui-infinite-image-browsing/.env +3 -0
- extensions/sd-webui-infinite-image-browsing/.gitignore +0 -17
- forcePull.bat +11 -0
commit.bat
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
@echo off
|
| 2 |
+
|
| 3 |
+
git config user.name "anonymous"
|
| 4 |
+
git config user.email my@gmail.com
|
| 5 |
+
|
| 6 |
+
set /P comment=Comment?
|
| 7 |
+
|
| 8 |
+
if not "%comment%" == "" goto :next
|
| 9 |
+
set comment=auto
|
| 10 |
+
:next
|
| 11 |
+
|
| 12 |
+
git reset
|
| 13 |
+
|
| 14 |
+
::git add *.* --force
|
| 15 |
+
::git add *
|
| 16 |
+
git add --all
|
| 17 |
+
|
| 18 |
+
echo ----- git add done ------
|
| 19 |
+
|
| 20 |
+
git status
|
| 21 |
+
|
| 22 |
+
echo ready. press any key to commit START.
|
| 23 |
+
::timeout /t 1
|
| 24 |
+
pause
|
| 25 |
+
|
| 26 |
+
git commit -m "%comment%"
|
| 27 |
+
|
| 28 |
+
git push -u origin main
|
| 29 |
+
|
| 30 |
+
pause
|
extensions/Stable-Diffusion-Webui-Civitai-Helper/about_version2.md
DELETED
|
@@ -1,30 +0,0 @@
|
|
| 1 |
-
## About Civitai Helper2: Model Info Helper
|
| 2 |
-
The first version of "Civitai Helper2" is already finished and I have used it for a while on my local machine. It works exactly as in the demo video I have released before(with some more functions).
|
| 3 |
-
|
| 4 |
-
[https://www.youtube.com/watch?v=mPcKwQDDH8s](https://www.youtube.com/watch?v=mPcKwQDDH8s)
|
| 5 |
-
|
| 6 |
-
But the developing process is extremely painful. Its maintenance will also be difficult. If I release it, with all those requests and issues from users, my life gonna be in hell.
|
| 7 |
-
|
| 8 |
-
The reason for this is, gradio is not suitable for a professional level's management system. But under gradio's framework and SD webui's design, a lot of javascript things won't work well.
|
| 9 |
-
|
| 10 |
-
So, it is too painful that I don't want to do any further maintenance and development on it. In the meantime, a lot of alternatives become popular. For example, ComfyUI and InvokeAI 3.x.
|
| 11 |
-
|
| 12 |
-
Especially InvokeAI 3.x, with its Unified Canvas and build-in node system, now we have a photoshop level's SD tool for free.
|
| 13 |
-
|
| 14 |
-
If you are not familiar with InvokeAI 3.x, you can check this video:
|
| 15 |
-
[https://www.youtube.com/watch?v=1Iz4F7o6hgQ](https://www.youtube.com/watch?v=1Iz4F7o6hgQ)
|
| 16 |
-
And its unified canvas system:
|
| 17 |
-
[https://www.youtube.com/watch?v=aU0jGZpDIVc](https://www.youtube.com/watch?v=aU0jGZpDIVc)
|
| 18 |
-
|
| 19 |
-
So, for Model Info Helper, I'm re-designing it into a stand alone model and workflow management system, which should work with most popular SD UI, not just for SD webui.
|
| 20 |
-
|
| 21 |
-
And I'll take down all workflow management functions so this stand alone version can be released sooner.
|
| 22 |
-
|
| 23 |
-
Since it won't be released as estimated, I have updated the old civitai helper 1.x to work with latest SD webui.
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
extensions/Stable-Diffusion-Webui-Civitai-Helper/javascript/civitai_helper.js
CHANGED
|
@@ -1,38 +1,61 @@
|
|
| 1 |
"use strict";
|
| 2 |
|
| 3 |
|
| 4 |
-
function ch_convert_file_path_to_url(path){
|
| 5 |
let prefix = "file=";
|
| 6 |
let path_to_url = path.replaceAll('\\', '/');
|
| 7 |
return prefix+path_to_url;
|
| 8 |
}
|
| 9 |
|
| 10 |
-
function ch_img_node_str(path){
|
| 11 |
return `<img src='${ch_convert_file_path_to_url(path)}' style="width:24px"/>`;
|
| 12 |
}
|
| 13 |
|
| 14 |
-
|
| 15 |
-
function ch_gradio_version(){
|
| 16 |
let foot = gradioApp().getElementById("footer");
|
| 17 |
-
if (!foot){return null;}
|
| 18 |
|
| 19 |
let versions = foot.querySelector(".versions");
|
| 20 |
-
if (!versions){return null;}
|
| 21 |
|
| 22 |
-
if (versions.
|
| 23 |
return "3.16.2";
|
| 24 |
} else {
|
| 25 |
return "3.23.0";
|
| 26 |
}
|
| 27 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 28 |
}
|
| 29 |
|
| 30 |
|
| 31 |
// send msg to python side by filling a hidden text box
|
| 32 |
// then will click a button to trigger an action
|
| 33 |
// msg is an object, not a string, will be stringify in this function
|
| 34 |
-
function send_ch_py_msg(msg){
|
| 35 |
-
console.log("run send_ch_py_msg")
|
| 36 |
let js_msg_txtbox = gradioApp().querySelector("#ch_js_msg_txtbox textarea");
|
| 37 |
if (js_msg_txtbox && msg) {
|
| 38 |
// fill to msg box
|
|
@@ -44,16 +67,16 @@ function send_ch_py_msg(msg){
|
|
| 44 |
|
| 45 |
// get msg from python side from a hidden textbox
|
| 46 |
// normally this is an old msg, need to wait for a new msg
|
| 47 |
-
function get_ch_py_msg(){
|
| 48 |
-
console.log("run get_ch_py_msg")
|
| 49 |
const py_msg_txtbox = gradioApp().querySelector("#ch_py_msg_txtbox textarea");
|
| 50 |
if (py_msg_txtbox && py_msg_txtbox.value) {
|
| 51 |
console.log("find py_msg_txtbox");
|
| 52 |
console.log("py_msg_txtbox value: ");
|
| 53 |
-
console.log(py_msg_txtbox.value)
|
| 54 |
-
return py_msg_txtbox.value
|
| 55 |
} else {
|
| 56 |
-
return ""
|
| 57 |
}
|
| 58 |
}
|
| 59 |
|
|
@@ -61,7 +84,7 @@ function get_ch_py_msg(){
|
|
| 61 |
// get msg from python side from a hidden textbox
|
| 62 |
// it will try once in every sencond, until it reach the max try times
|
| 63 |
const get_new_ch_py_msg = (max_count=5) => new Promise((resolve, reject) => {
|
| 64 |
-
console.log("run get_new_ch_py_msg")
|
| 65 |
|
| 66 |
let count = 0;
|
| 67 |
let new_msg = "";
|
|
@@ -73,11 +96,11 @@ const get_new_ch_py_msg = (max_count=5) => new Promise((resolve, reject) => {
|
|
| 73 |
if (py_msg_txtbox && py_msg_txtbox.value) {
|
| 74 |
console.log("find py_msg_txtbox");
|
| 75 |
console.log("py_msg_txtbox value: ");
|
| 76 |
-
console.log(py_msg_txtbox.value)
|
| 77 |
|
| 78 |
-
new_msg = py_msg_txtbox.value
|
| 79 |
if (new_msg != "") {
|
| 80 |
-
find_msg=true
|
| 81 |
}
|
| 82 |
}
|
| 83 |
|
|
@@ -98,7 +121,7 @@ const get_new_ch_py_msg = (max_count=5) => new Promise((resolve, reject) => {
|
|
| 98 |
}
|
| 99 |
|
| 100 |
}, 1000);
|
| 101 |
-
})
|
| 102 |
|
| 103 |
|
| 104 |
function getActiveTabType() {
|
|
@@ -112,7 +135,9 @@ function getActiveTabType() {
|
|
| 112 |
return null;
|
| 113 |
}
|
| 114 |
|
| 115 |
-
|
|
|
|
|
|
|
| 116 |
|
| 117 |
function getActivePrompt() {
|
| 118 |
const currentTab = get_uiCurrentTabContent();
|
|
@@ -138,16 +163,20 @@ function getActiveNegativePrompt() {
|
|
| 138 |
|
| 139 |
|
| 140 |
//button's click function
|
| 141 |
-
async function open_model_url(event, model_type, search_term){
|
| 142 |
console.log("start open_model_url");
|
| 143 |
|
| 144 |
-
//
|
|
|
|
|
|
|
|
|
|
|
|
|
| 145 |
let js_open_url_btn = gradioApp().getElementById("ch_js_open_url_btn");
|
| 146 |
if (!js_open_url_btn) {
|
| 147 |
-
|
|
|
|
| 148 |
}
|
| 149 |
|
| 150 |
-
|
| 151 |
//msg to python side
|
| 152 |
let msg = {
|
| 153 |
"action": "",
|
|
@@ -155,8 +184,7 @@ async function open_model_url(event, model_type, search_term){
|
|
| 155 |
"search_term": "",
|
| 156 |
"prompt": "",
|
| 157 |
"neg_prompt": "",
|
| 158 |
-
}
|
| 159 |
-
|
| 160 |
|
| 161 |
msg["action"] = "open_url";
|
| 162 |
msg["model_type"] = model_type;
|
|
@@ -165,24 +193,19 @@ async function open_model_url(event, model_type, search_term){
|
|
| 165 |
msg["neg_prompt"] = "";
|
| 166 |
|
| 167 |
// fill to msg box
|
| 168 |
-
send_ch_py_msg(msg)
|
| 169 |
|
| 170 |
//click hidden button
|
| 171 |
js_open_url_btn.click();
|
| 172 |
|
| 173 |
-
// stop parent event
|
| 174 |
-
event.stopPropagation()
|
| 175 |
-
event.preventDefault()
|
| 176 |
-
|
| 177 |
//check response msg from python
|
| 178 |
-
let new_py_msg =
|
| 179 |
try {
|
| 180 |
new_py_msg = await get_new_ch_py_msg();
|
| 181 |
-
|
| 182 |
} catch (error) {
|
| 183 |
-
console.log(error);
|
| 184 |
}
|
| 185 |
-
|
| 186 |
console.log("new_py_msg:");
|
| 187 |
console.log(new_py_msg);
|
| 188 |
|
|
@@ -194,28 +217,25 @@ async function open_model_url(event, model_type, search_term){
|
|
| 194 |
if (py_msg_json.content.url) {
|
| 195 |
window.open(py_msg_json.content.url, "_blank");
|
| 196 |
}
|
| 197 |
-
|
| 198 |
}
|
| 199 |
-
|
| 200 |
-
|
| 201 |
}
|
| 202 |
|
| 203 |
-
|
| 204 |
console.log("end open_model_url");
|
| 205 |
|
| 206 |
-
|
| 207 |
}
|
| 208 |
|
| 209 |
-
function add_trigger_words(event, model_type, search_term){
|
| 210 |
console.log("start add_trigger_words");
|
| 211 |
|
| 212 |
-
|
|
|
|
|
|
|
|
|
|
| 213 |
let js_add_trigger_words_btn = gradioApp().getElementById("ch_js_add_trigger_words_btn");
|
| 214 |
if (!js_add_trigger_words_btn) {
|
| 215 |
-
return
|
| 216 |
}
|
| 217 |
|
| 218 |
-
|
| 219 |
//msg to python side
|
| 220 |
let msg = {
|
| 221 |
"action": "",
|
|
@@ -223,7 +243,7 @@ function add_trigger_words(event, model_type, search_term){
|
|
| 223 |
"search_term": "",
|
| 224 |
"prompt": "",
|
| 225 |
"neg_prompt": "",
|
| 226 |
-
}
|
| 227 |
|
| 228 |
msg["action"] = "add_trigger_words";
|
| 229 |
msg["model_type"] = model_type;
|
|
@@ -235,26 +255,25 @@ function add_trigger_words(event, model_type, search_term){
|
|
| 235 |
msg["prompt"] = act_prompt.value;
|
| 236 |
|
| 237 |
// fill to msg box
|
| 238 |
-
send_ch_py_msg(msg)
|
| 239 |
|
| 240 |
//click hidden button
|
| 241 |
js_add_trigger_words_btn.click();
|
| 242 |
|
| 243 |
console.log("end add_trigger_words");
|
| 244 |
|
| 245 |
-
event.stopPropagation()
|
| 246 |
-
event.preventDefault()
|
| 247 |
-
|
| 248 |
-
|
| 249 |
}
|
| 250 |
|
| 251 |
-
function use_preview_prompt(event, model_type, search_term){
|
| 252 |
console.log("start use_preview_prompt");
|
| 253 |
|
| 254 |
-
|
|
|
|
|
|
|
|
|
|
| 255 |
let js_use_preview_prompt_btn = gradioApp().getElementById("ch_js_use_preview_prompt_btn");
|
| 256 |
if (!js_use_preview_prompt_btn) {
|
| 257 |
-
return
|
| 258 |
}
|
| 259 |
|
| 260 |
//msg to python side
|
|
@@ -264,7 +283,7 @@ function use_preview_prompt(event, model_type, search_term){
|
|
| 264 |
"search_term": "",
|
| 265 |
"prompt": "",
|
| 266 |
"neg_prompt": "",
|
| 267 |
-
}
|
| 268 |
|
| 269 |
msg["action"] = "use_preview_prompt";
|
| 270 |
msg["model_type"] = model_type;
|
|
@@ -279,35 +298,64 @@ function use_preview_prompt(event, model_type, search_term){
|
|
| 279 |
msg["neg_prompt"] = neg_prompt.value;
|
| 280 |
|
| 281 |
// fill to msg box
|
| 282 |
-
send_ch_py_msg(msg)
|
| 283 |
|
| 284 |
//click hidden button
|
| 285 |
js_use_preview_prompt_btn.click();
|
| 286 |
|
| 287 |
console.log("end use_preview_prompt");
|
| 288 |
|
| 289 |
-
|
| 290 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 291 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 292 |
}
|
| 293 |
|
| 294 |
|
| 295 |
-
async function remove_card(event, model_type, search_term){
|
| 296 |
console.log("start remove_card");
|
| 297 |
|
| 298 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 299 |
let js_remove_card_btn = gradioApp().getElementById("ch_js_remove_card_btn");
|
| 300 |
if (!js_remove_card_btn) {
|
| 301 |
-
return
|
| 302 |
}
|
| 303 |
|
| 304 |
// must confirm before removing
|
| 305 |
-
let rm_confirm = "\nConfirm to remove this model.
|
| 306 |
if (!confirm(rm_confirm)) {
|
| 307 |
-
return
|
| 308 |
}
|
| 309 |
|
| 310 |
-
|
| 311 |
//msg to python side
|
| 312 |
let msg = {
|
| 313 |
"action": "",
|
|
@@ -315,12 +363,9 @@ async function remove_card(event, model_type, search_term){
|
|
| 315 |
"search_term": "",
|
| 316 |
}
|
| 317 |
|
| 318 |
-
|
| 319 |
msg["action"] = "remove_card";
|
| 320 |
msg["model_type"] = model_type;
|
| 321 |
msg["search_term"] = search_term;
|
| 322 |
-
msg["prompt"] = "";
|
| 323 |
-
msg["neg_prompt"] = "";
|
| 324 |
|
| 325 |
// fill to msg box
|
| 326 |
send_ch_py_msg(msg)
|
|
@@ -328,10 +373,76 @@ async function remove_card(event, model_type, search_term){
|
|
| 328 |
//click hidden button
|
| 329 |
js_remove_card_btn.click();
|
| 330 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 331 |
// stop parent event
|
| 332 |
event.stopPropagation()
|
| 333 |
event.preventDefault()
|
| 334 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 335 |
//check response msg from python
|
| 336 |
let new_py_msg = "";
|
| 337 |
try {
|
|
@@ -340,7 +451,7 @@ async function remove_card(event, model_type, search_term){
|
|
| 340 |
console.log(error);
|
| 341 |
new_py_msg = error;
|
| 342 |
}
|
| 343 |
-
|
| 344 |
console.log("new_py_msg:");
|
| 345 |
console.log(new_py_msg);
|
| 346 |
|
|
@@ -351,44 +462,49 @@ async function remove_card(event, model_type, search_term){
|
|
| 351 |
result = new_py_msg;
|
| 352 |
}
|
| 353 |
|
| 354 |
-
|
| 355 |
-
|
| 356 |
-
|
| 357 |
-
if (result=="Done"){
|
| 358 |
-
console.log("refresh card list");
|
| 359 |
-
//refresh card list
|
| 360 |
-
let active_tab = getActiveTabType();
|
| 361 |
-
console.log("get active tab id: " + active_tab);
|
| 362 |
-
if (active_tab){
|
| 363 |
-
let refresh_btn_id = active_tab + "_extra_refresh";
|
| 364 |
-
let refresh_btn = gradioApp().getElementById(refresh_btn_id);
|
| 365 |
-
if (refresh_btn){
|
| 366 |
-
console.log("click button: "+refresh_btn_id);
|
| 367 |
-
refresh_btn.click();
|
| 368 |
-
}
|
| 369 |
-
}
|
| 370 |
}
|
| 371 |
-
|
| 372 |
-
console.log("end
|
|
|
|
|
|
|
| 373 |
|
| 374 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 375 |
}
|
| 376 |
|
| 377 |
|
| 378 |
// download model's new version into SD at python side
|
| 379 |
-
function ch_dl_model_new_version(event, model_path, version_id, download_url){
|
| 380 |
console.log("start ch_dl_model_new_version");
|
| 381 |
|
|
|
|
|
|
|
|
|
|
| 382 |
// must confirm before downloading
|
| 383 |
let dl_confirm = "\nConfirm to download.\n\nCheck Download Model Section's log and console log for detail.";
|
|
|
|
| 384 |
if (!confirm(dl_confirm)) {
|
| 385 |
-
return
|
| 386 |
}
|
| 387 |
|
| 388 |
-
//get hidden components of extension
|
| 389 |
let js_dl_model_new_version_btn = gradioApp().getElementById("ch_js_dl_model_new_version_btn");
|
| 390 |
if (!js_dl_model_new_version_btn) {
|
| 391 |
-
return
|
| 392 |
}
|
| 393 |
|
| 394 |
//msg to python side
|
|
@@ -397,58 +513,463 @@ function ch_dl_model_new_version(event, model_path, version_id, download_url){
|
|
| 397 |
"model_path": "",
|
| 398 |
"version_id": "",
|
| 399 |
"download_url": "",
|
| 400 |
-
}
|
| 401 |
|
| 402 |
msg["action"] = "dl_model_new_version";
|
| 403 |
msg["model_path"] = model_path;
|
| 404 |
msg["version_id"] = version_id;
|
| 405 |
msg["download_url"] = download_url;
|
|
|
|
| 406 |
|
| 407 |
// fill to msg box
|
| 408 |
-
send_ch_py_msg(msg)
|
| 409 |
|
| 410 |
//click hidden button
|
| 411 |
js_dl_model_new_version_btn.click();
|
| 412 |
|
| 413 |
console.log("end dl_model_new_version");
|
| 414 |
|
| 415 |
-
event.stopPropagation()
|
| 416 |
-
event.preventDefault()
|
| 417 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 418 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 419 |
}
|
| 420 |
|
| 421 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 422 |
onUiLoaded(() => {
|
| 423 |
|
| 424 |
//get gradio version
|
| 425 |
-
|
| 426 |
-
console.log("
|
| 427 |
|
| 428 |
// get all extra network tabs
|
| 429 |
-
|
| 430 |
-
|
| 431 |
-
let cardid_suffix = "cards";
|
| 432 |
-
|
| 433 |
-
//get init py msg
|
| 434 |
-
// let init_py_msg_str = get_ch_py_msg();
|
| 435 |
-
// let extension_path = "";
|
| 436 |
-
// if (!init_py_msg_str) {
|
| 437 |
-
// console.log("Can not get init_py_msg");
|
| 438 |
-
// } else {
|
| 439 |
-
// init_py_msg = JSON.parse(init_py_msg_str);
|
| 440 |
-
// if (init_py_msg) {
|
| 441 |
-
// extension_path = init_py_msg.extension_path;
|
| 442 |
-
// console.log("get extension path: " + extension_path);
|
| 443 |
-
// }
|
| 444 |
-
// }
|
| 445 |
-
|
| 446 |
-
// //icon image node as string
|
| 447 |
-
// function icon(icon_name){
|
| 448 |
-
// let icon_path = extension_path+"/icon/"+icon_name;
|
| 449 |
-
// return ch_img_node_str(icon_path);
|
| 450 |
-
// }
|
| 451 |
-
|
| 452 |
|
| 453 |
// update extra network tab pages' cards
|
| 454 |
// * replace "replace preview" text button into an icon
|
|
@@ -459,87 +980,39 @@ onUiLoaded(() => {
|
|
| 459 |
// notice: javascript can not get response from python side
|
| 460 |
// so, these buttons just sent request to python
|
| 461 |
// then, python side gonna open url and update prompt text box, without telling js side.
|
| 462 |
-
function update_card_for_civitai(){
|
| 463 |
-
|
| 464 |
-
|
| 465 |
-
|
| 466 |
-
|
| 467 |
-
|
| 468 |
-
let btn_thumb_display = "inline";
|
| 469 |
-
let btn_thumb_pos = "static";
|
| 470 |
-
let btn_thumb_backgroundImage = "none";
|
| 471 |
-
let btn_thumb_background = "rgba(0, 0, 0, 0.8)";
|
| 472 |
-
|
| 473 |
-
let ch_btn_txts = ['🌐', '💡', '🏷️'];
|
| 474 |
let replace_preview_text = getTranslation("replace preview");
|
| 475 |
if (!replace_preview_text) {
|
| 476 |
replace_preview_text = "replace preview";
|
| 477 |
}
|
| 478 |
|
| 479 |
-
|
| 480 |
-
//change all "replace preview" into an icon
|
| 481 |
-
let extra_network_id = "";
|
| 482 |
let extra_network_node = null;
|
| 483 |
-
let button_row = null;
|
| 484 |
-
let search_term_node = null;
|
| 485 |
-
let search_term = "";
|
| 486 |
let model_type = "";
|
| 487 |
let cards = null;
|
| 488 |
-
let need_to_add_buttons = false;
|
| 489 |
|
| 490 |
//get current tab
|
| 491 |
let active_tab_type = getActiveTabType();
|
| 492 |
-
if (!active_tab_type){active_tab_type = "txt2img";}
|
| 493 |
|
| 494 |
for (const tab_prefix of tab_prefix_list) {
|
| 495 |
if (tab_prefix != active_tab_type) {continue;}
|
| 496 |
|
| 497 |
-
|
| 498 |
-
//find out current selected model type tab
|
| 499 |
-
let active_extra_tab_type = "";
|
| 500 |
-
let extra_tabs = gradioApp().getElementById(tab_prefix+"_extra_tabs");
|
| 501 |
-
if (!extra_tabs) {console.log("can not find extra_tabs: " + tab_prefix+"_extra_tabs");}
|
| 502 |
-
|
| 503 |
//get active extratab
|
|
|
|
| 504 |
const active_extra_tab = Array.from(get_uiCurrentTabContent().querySelectorAll('.extra-network-cards,.extra-network-thumbs'))
|
| 505 |
.find(el => el.closest('.tabitem').style.display === 'block')
|
| 506 |
-
?.id.match(
|
| 507 |
-
|
| 508 |
-
|
| 509 |
-
console.log("found active tab: " + active_extra_tab);
|
| 510 |
-
|
| 511 |
-
switch (active_extra_tab) {
|
| 512 |
-
case "textual_inversion":
|
| 513 |
-
active_extra_tab_type = "ti";
|
| 514 |
-
break;
|
| 515 |
-
case "hypernetworks":
|
| 516 |
-
active_extra_tab_type = "hyper";
|
| 517 |
-
break;
|
| 518 |
-
case "checkpoints":
|
| 519 |
-
active_extra_tab_type = "ckp";
|
| 520 |
-
break;
|
| 521 |
-
case "lora":
|
| 522 |
-
active_extra_tab_type = "lora";
|
| 523 |
-
break;
|
| 524 |
-
}
|
| 525 |
|
|
|
|
| 526 |
|
| 527 |
for (const js_model_type of model_type_list) {
|
| 528 |
//get model_type for python side
|
| 529 |
-
|
| 530 |
-
case "textual_inversion":
|
| 531 |
-
model_type = "ti";
|
| 532 |
-
break;
|
| 533 |
-
case "hypernetworks":
|
| 534 |
-
model_type = "hyper";
|
| 535 |
-
break;
|
| 536 |
-
case "checkpoints":
|
| 537 |
-
model_type = "ckp";
|
| 538 |
-
break;
|
| 539 |
-
case "lora":
|
| 540 |
-
model_type = "lora";
|
| 541 |
-
break;
|
| 542 |
-
}
|
| 543 |
|
| 544 |
if (!model_type) {
|
| 545 |
console.log("can not get model_type from: " + js_model_type);
|
|
@@ -552,142 +1025,75 @@ onUiLoaded(() => {
|
|
| 552 |
continue;
|
| 553 |
}
|
| 554 |
|
| 555 |
-
console.log("handle active extra tab");
|
| 556 |
-
|
| 557 |
|
| 558 |
-
extra_network_id = tab_prefix+"_"+js_model_type+"_"+cardid_suffix;
|
| 559 |
// console.log("searching extra_network_node: " + extra_network_id);
|
| 560 |
-
extra_network_node =
|
| 561 |
-
|
| 562 |
-
// console.log("find extra_network_node: " + extra_network_id);
|
| 563 |
|
| 564 |
// get all card nodes
|
| 565 |
cards = extra_network_node.querySelectorAll(".card");
|
| 566 |
-
for (
|
| 567 |
-
//
|
| 568 |
-
|
| 569 |
-
|
| 570 |
-
|
| 571 |
-
console.log(
|
| 572 |
-
continue;
|
| 573 |
-
}
|
| 574 |
-
|
| 575 |
-
let atags = button_row.querySelectorAll("a");
|
| 576 |
-
if (atags && atags.length) {
|
| 577 |
-
console.log("find atags: " + atags.length);
|
| 578 |
-
} else {
|
| 579 |
-
console.log("no atags");
|
| 580 |
-
need_to_add_buttons = true;
|
| 581 |
-
}
|
| 582 |
-
|
| 583 |
-
if (!need_to_add_buttons) {
|
| 584 |
-
console.log("do not need to add buttons");
|
| 585 |
-
continue;
|
| 586 |
-
}
|
| 587 |
-
|
| 588 |
-
|
| 589 |
-
// search_term node
|
| 590 |
-
// search_term = subfolder path + model name + ext
|
| 591 |
-
search_term_node = card.querySelector(".actions .additional .search_term");
|
| 592 |
-
if (!search_term_node){
|
| 593 |
-
console.log("can not find search_term node for cards in " + extra_network_id);
|
| 594 |
-
continue;
|
| 595 |
-
}
|
| 596 |
-
|
| 597 |
-
// get search_term
|
| 598 |
-
search_term = search_term_node.innerHTML.trim();
|
| 599 |
-
if (!search_term) {
|
| 600 |
-
console.log("search_term is empty for cards in " + extra_network_id);
|
| 601 |
-
continue;
|
| 602 |
}
|
| 603 |
-
|
| 604 |
-
console.log("adding buttons");
|
| 605 |
-
// then we need to add 3 buttons to each ul node:
|
| 606 |
-
let open_url_node = document.createElement("a");
|
| 607 |
-
open_url_node.href = "#";
|
| 608 |
-
open_url_node.innerHTML = "🌐";
|
| 609 |
-
open_url_node.className = "card-button";
|
| 610 |
-
|
| 611 |
-
open_url_node.title = "Open this model's civitai url";
|
| 612 |
-
open_url_node.setAttribute("onclick","open_model_url(event, '"+model_type+"', '"+search_term+"')");
|
| 613 |
-
|
| 614 |
-
let add_trigger_words_node = document.createElement("a");
|
| 615 |
-
add_trigger_words_node.href = "#";
|
| 616 |
-
add_trigger_words_node.innerHTML = "💡";
|
| 617 |
-
add_trigger_words_node.className = "card-button";
|
| 618 |
-
|
| 619 |
-
add_trigger_words_node.title = "Add trigger words to prompt";
|
| 620 |
-
add_trigger_words_node.setAttribute("onclick","add_trigger_words(event, '"+model_type+"', '"+search_term+"')");
|
| 621 |
-
|
| 622 |
-
let use_preview_prompt_node = document.createElement("a");
|
| 623 |
-
use_preview_prompt_node.href = "#";
|
| 624 |
-
use_preview_prompt_node.innerHTML = "🏷️";
|
| 625 |
-
use_preview_prompt_node.className = "card-button";
|
| 626 |
-
|
| 627 |
-
use_preview_prompt_node.title = "Use prompt from preview image";
|
| 628 |
-
use_preview_prompt_node.setAttribute("onclick","use_preview_prompt(event, '"+model_type+"', '"+search_term+"')");
|
| 629 |
-
|
| 630 |
-
let remove_card_node = document.createElement("a");
|
| 631 |
-
remove_card_node.href = "#";
|
| 632 |
-
remove_card_node.innerHTML = "❌";
|
| 633 |
-
remove_card_node.className = "card-button";
|
| 634 |
-
|
| 635 |
-
remove_card_node.title = "Remove this model";
|
| 636 |
-
remove_card_node.setAttribute("onclick","remove_card(event, '"+model_type+"', '"+search_term+"')");
|
| 637 |
-
|
| 638 |
-
//add to card
|
| 639 |
-
button_row.appendChild(open_url_node);
|
| 640 |
-
button_row.appendChild(add_trigger_words_node);
|
| 641 |
-
button_row.appendChild(use_preview_prompt_node);
|
| 642 |
-
button_row.appendChild(remove_card_node);
|
| 643 |
-
|
| 644 |
-
|
| 645 |
-
|
| 646 |
-
|
| 647 |
}
|
| 648 |
|
| 649 |
-
|
| 650 |
}
|
| 651 |
-
}
|
| 652 |
|
|
|
|
| 653 |
|
| 654 |
}
|
| 655 |
|
| 656 |
-
|
| 657 |
-
let tab_id = ""
|
| 658 |
-
let extra_tab = null;
|
| 659 |
-
let extra_toolbar = null;
|
| 660 |
let extra_network_refresh_btn = null;
|
|
|
|
|
|
|
|
|
|
| 661 |
//add refresh button to extra network's toolbar
|
| 662 |
-
for (
|
| 663 |
-
|
| 664 |
-
|
| 665 |
|
| 666 |
-
//get toolbar
|
| 667 |
-
//get Refresh button
|
| 668 |
-
extra_network_refresh_btn = gradioApp().getElementById(prefix+"_extra_refresh");
|
| 669 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 670 |
|
| 671 |
-
|
| 672 |
-
|
| 673 |
continue;
|
|
|
|
| 674 |
}
|
| 675 |
|
| 676 |
-
//
|
| 677 |
-
|
| 678 |
-
|
| 679 |
-
ch_refresh.title = "Refresh Civitai Helper's additional buttons";
|
| 680 |
-
ch_refresh.className = "lg secondary gradio-button";
|
| 681 |
-
ch_refresh.style.fontSize = "200%";
|
| 682 |
-
ch_refresh.onclick = update_card_for_civitai;
|
| 683 |
|
| 684 |
-
|
|
|
|
| 685 |
|
| 686 |
-
|
|
|
|
|
|
|
|
|
|
| 687 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 688 |
|
| 689 |
//run it once
|
| 690 |
-
update_card_for_civitai();
|
| 691 |
|
| 692 |
|
| 693 |
});
|
|
|
|
| 1 |
"use strict";
|
| 2 |
|
| 3 |
|
| 4 |
+
function ch_convert_file_path_to_url(path) {
|
| 5 |
let prefix = "file=";
|
| 6 |
let path_to_url = path.replaceAll('\\', '/');
|
| 7 |
return prefix+path_to_url;
|
| 8 |
}
|
| 9 |
|
| 10 |
+
function ch_img_node_str(path) {
|
| 11 |
return `<img src='${ch_convert_file_path_to_url(path)}' style="width:24px"/>`;
|
| 12 |
}
|
| 13 |
|
| 14 |
+
function ch_gradio_version() {
|
|
|
|
| 15 |
let foot = gradioApp().getElementById("footer");
|
| 16 |
+
if (!foot) { return null; }
|
| 17 |
|
| 18 |
let versions = foot.querySelector(".versions");
|
| 19 |
+
if (!versions) { return null; }
|
| 20 |
|
| 21 |
+
if (versions.textContent.indexOf("gradio: 3.16.2")>0) {
|
| 22 |
return "3.16.2";
|
| 23 |
} else {
|
| 24 |
return "3.23.0";
|
| 25 |
}
|
| 26 |
+
}
|
| 27 |
+
|
| 28 |
+
/*
|
| 29 |
+
* Functions for scan for duplicates elements.
|
| 30 |
+
*/
|
| 31 |
+
|
| 32 |
+
let ch_path_el = null;
|
| 33 |
+
function display_ch_path(event, path) {
|
| 34 |
+
if (!ch_path_el) {
|
| 35 |
+
ch_path_el = document.createElement("div");
|
| 36 |
+
ch_path_el.id = "ch_path_el";
|
| 37 |
+
document.body.appendChild(ch_path_el);
|
| 38 |
+
}
|
| 39 |
+
|
| 40 |
+
ch_path_el.textContent = path;
|
| 41 |
+
ch_path_el.style.display = "block";
|
| 42 |
+
}
|
| 43 |
+
|
| 44 |
+
function move_ch_path(event) {
|
| 45 |
+
ch_path_el.style.top = `calc(${event.clientY}px - 2em)`;
|
| 46 |
+
ch_path_el.style.left = `calc(${event.clientX}px + 2em)`;
|
| 47 |
+
}
|
| 48 |
+
|
| 49 |
+
function hide_ch_path(event) {
|
| 50 |
+
ch_path_el.style.display = "none";
|
| 51 |
}
|
| 52 |
|
| 53 |
|
| 54 |
// send msg to python side by filling a hidden text box
|
| 55 |
// then will click a button to trigger an action
|
| 56 |
// msg is an object, not a string, will be stringify in this function
|
| 57 |
+
function send_ch_py_msg(msg) {
|
| 58 |
+
console.log("run send_ch_py_msg");
|
| 59 |
let js_msg_txtbox = gradioApp().querySelector("#ch_js_msg_txtbox textarea");
|
| 60 |
if (js_msg_txtbox && msg) {
|
| 61 |
// fill to msg box
|
|
|
|
| 67 |
|
| 68 |
// get msg from python side from a hidden textbox
|
| 69 |
// normally this is an old msg, need to wait for a new msg
|
| 70 |
+
function get_ch_py_msg() {
|
| 71 |
+
console.log("run get_ch_py_msg");
|
| 72 |
const py_msg_txtbox = gradioApp().querySelector("#ch_py_msg_txtbox textarea");
|
| 73 |
if (py_msg_txtbox && py_msg_txtbox.value) {
|
| 74 |
console.log("find py_msg_txtbox");
|
| 75 |
console.log("py_msg_txtbox value: ");
|
| 76 |
+
console.log(py_msg_txtbox.value);
|
| 77 |
+
return py_msg_txtbox.value;
|
| 78 |
} else {
|
| 79 |
+
return "";
|
| 80 |
}
|
| 81 |
}
|
| 82 |
|
|
|
|
| 84 |
// get msg from python side from a hidden textbox
|
| 85 |
// it will try once in every sencond, until it reach the max try times
|
| 86 |
const get_new_ch_py_msg = (max_count=5) => new Promise((resolve, reject) => {
|
| 87 |
+
console.log("run get_new_ch_py_msg");
|
| 88 |
|
| 89 |
let count = 0;
|
| 90 |
let new_msg = "";
|
|
|
|
| 96 |
if (py_msg_txtbox && py_msg_txtbox.value) {
|
| 97 |
console.log("find py_msg_txtbox");
|
| 98 |
console.log("py_msg_txtbox value: ");
|
| 99 |
+
console.log(py_msg_txtbox.value);
|
| 100 |
|
| 101 |
+
new_msg = py_msg_txtbox.value;
|
| 102 |
if (new_msg != "") {
|
| 103 |
+
find_msg = true;
|
| 104 |
}
|
| 105 |
}
|
| 106 |
|
|
|
|
| 121 |
}
|
| 122 |
|
| 123 |
}, 1000);
|
| 124 |
+
});
|
| 125 |
|
| 126 |
|
| 127 |
function getActiveTabType() {
|
|
|
|
| 135 |
return null;
|
| 136 |
}
|
| 137 |
|
| 138 |
+
function getExtraTabs(prefix) {
|
| 139 |
+
return gradioApp().getElementById(prefix + "_extra_tabs");
|
| 140 |
+
}
|
| 141 |
|
| 142 |
function getActivePrompt() {
|
| 143 |
const currentTab = get_uiCurrentTabContent();
|
|
|
|
| 163 |
|
| 164 |
|
| 165 |
//button's click function
|
| 166 |
+
async function open_model_url(event, model_type, search_term) {
|
| 167 |
console.log("start open_model_url");
|
| 168 |
|
| 169 |
+
// stop parent event
|
| 170 |
+
event.stopPropagation();
|
| 171 |
+
event.preventDefault();
|
| 172 |
+
|
| 173 |
+
//get hidden components of extension
|
| 174 |
let js_open_url_btn = gradioApp().getElementById("ch_js_open_url_btn");
|
| 175 |
if (!js_open_url_btn) {
|
| 176 |
+
console.log("Failed to find js_open_url_btn");
|
| 177 |
+
return;
|
| 178 |
}
|
| 179 |
|
|
|
|
| 180 |
//msg to python side
|
| 181 |
let msg = {
|
| 182 |
"action": "",
|
|
|
|
| 184 |
"search_term": "",
|
| 185 |
"prompt": "",
|
| 186 |
"neg_prompt": "",
|
| 187 |
+
};
|
|
|
|
| 188 |
|
| 189 |
msg["action"] = "open_url";
|
| 190 |
msg["model_type"] = model_type;
|
|
|
|
| 193 |
msg["neg_prompt"] = "";
|
| 194 |
|
| 195 |
// fill to msg box
|
| 196 |
+
send_ch_py_msg(msg);
|
| 197 |
|
| 198 |
//click hidden button
|
| 199 |
js_open_url_btn.click();
|
| 200 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 201 |
//check response msg from python
|
| 202 |
+
let new_py_msg = null;
|
| 203 |
try {
|
| 204 |
new_py_msg = await get_new_ch_py_msg();
|
|
|
|
| 205 |
} catch (error) {
|
| 206 |
+
console.log(error);
|
| 207 |
}
|
| 208 |
+
|
| 209 |
console.log("new_py_msg:");
|
| 210 |
console.log(new_py_msg);
|
| 211 |
|
|
|
|
| 217 |
if (py_msg_json.content.url) {
|
| 218 |
window.open(py_msg_json.content.url, "_blank");
|
| 219 |
}
|
|
|
|
| 220 |
}
|
|
|
|
|
|
|
| 221 |
}
|
| 222 |
|
|
|
|
| 223 |
console.log("end open_model_url");
|
| 224 |
|
|
|
|
| 225 |
}
|
| 226 |
|
| 227 |
+
function add_trigger_words(event, model_type, search_term) {
|
| 228 |
console.log("start add_trigger_words");
|
| 229 |
|
| 230 |
+
event.stopPropagation();
|
| 231 |
+
event.preventDefault();
|
| 232 |
+
|
| 233 |
+
//get hidden components of extension
|
| 234 |
let js_add_trigger_words_btn = gradioApp().getElementById("ch_js_add_trigger_words_btn");
|
| 235 |
if (!js_add_trigger_words_btn) {
|
| 236 |
+
return;
|
| 237 |
}
|
| 238 |
|
|
|
|
| 239 |
//msg to python side
|
| 240 |
let msg = {
|
| 241 |
"action": "",
|
|
|
|
| 243 |
"search_term": "",
|
| 244 |
"prompt": "",
|
| 245 |
"neg_prompt": "",
|
| 246 |
+
};
|
| 247 |
|
| 248 |
msg["action"] = "add_trigger_words";
|
| 249 |
msg["model_type"] = model_type;
|
|
|
|
| 255 |
msg["prompt"] = act_prompt.value;
|
| 256 |
|
| 257 |
// fill to msg box
|
| 258 |
+
send_ch_py_msg(msg);
|
| 259 |
|
| 260 |
//click hidden button
|
| 261 |
js_add_trigger_words_btn.click();
|
| 262 |
|
| 263 |
console.log("end add_trigger_words");
|
| 264 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 265 |
}
|
| 266 |
|
| 267 |
+
function use_preview_prompt(event, model_type, search_term) {
|
| 268 |
console.log("start use_preview_prompt");
|
| 269 |
|
| 270 |
+
event.stopPropagation();
|
| 271 |
+
event.preventDefault();
|
| 272 |
+
|
| 273 |
+
//get hidden components of extension
|
| 274 |
let js_use_preview_prompt_btn = gradioApp().getElementById("ch_js_use_preview_prompt_btn");
|
| 275 |
if (!js_use_preview_prompt_btn) {
|
| 276 |
+
return;
|
| 277 |
}
|
| 278 |
|
| 279 |
//msg to python side
|
|
|
|
| 283 |
"search_term": "",
|
| 284 |
"prompt": "",
|
| 285 |
"neg_prompt": "",
|
| 286 |
+
};
|
| 287 |
|
| 288 |
msg["action"] = "use_preview_prompt";
|
| 289 |
msg["model_type"] = model_type;
|
|
|
|
| 298 |
msg["neg_prompt"] = neg_prompt.value;
|
| 299 |
|
| 300 |
// fill to msg box
|
| 301 |
+
send_ch_py_msg(msg);
|
| 302 |
|
| 303 |
//click hidden button
|
| 304 |
js_use_preview_prompt_btn.click();
|
| 305 |
|
| 306 |
console.log("end use_preview_prompt");
|
| 307 |
|
| 308 |
+
}
|
| 309 |
+
|
| 310 |
+
|
| 311 |
+
async function remove_dup_card(event, model_type, search_term) {
|
| 312 |
+
event.stopPropagation();
|
| 313 |
+
event.preventDefault();
|
| 314 |
+
|
| 315 |
+
let el = event.currentTarget;
|
| 316 |
+
|
| 317 |
+
let success = await remove_card(event, model_type, search_term);
|
| 318 |
+
|
| 319 |
+
if (success === true) {
|
| 320 |
+
let parent = el.parentElement;
|
| 321 |
|
| 322 |
+
let sha256 = search_term.split(" ").pop().toUpperCase();
|
| 323 |
+
let row_id = `ch_${sha256}`;
|
| 324 |
+
let cards_id = `${row_id}_cards`;
|
| 325 |
+
|
| 326 |
+
let cards = document.getElementById(cards_id);
|
| 327 |
+
|
| 328 |
+
cards.removeChild(parent);
|
| 329 |
+
|
| 330 |
+
if (cards.children.length < 2) {
|
| 331 |
+
let row = document.getElementById(row_id);
|
| 332 |
+
row.parentElement.removeChild(row);
|
| 333 |
+
}
|
| 334 |
+
}
|
| 335 |
}
|
| 336 |
|
| 337 |
|
| 338 |
+
async function remove_card(event, model_type, search_term) {
|
| 339 |
console.log("start remove_card");
|
| 340 |
|
| 341 |
+
let status = false
|
| 342 |
+
|
| 343 |
+
// stop parent event
|
| 344 |
+
event.stopPropagation()
|
| 345 |
+
event.preventDefault()
|
| 346 |
+
|
| 347 |
+
//get hidden components of extension
|
| 348 |
let js_remove_card_btn = gradioApp().getElementById("ch_js_remove_card_btn");
|
| 349 |
if (!js_remove_card_btn) {
|
| 350 |
+
return status;
|
| 351 |
}
|
| 352 |
|
| 353 |
// must confirm before removing
|
| 354 |
+
let rm_confirm = "\nConfirm to remove this model and all related files. This process is irreversible.";
|
| 355 |
if (!confirm(rm_confirm)) {
|
| 356 |
+
return status;
|
| 357 |
}
|
| 358 |
|
|
|
|
| 359 |
//msg to python side
|
| 360 |
let msg = {
|
| 361 |
"action": "",
|
|
|
|
| 363 |
"search_term": "",
|
| 364 |
}
|
| 365 |
|
|
|
|
| 366 |
msg["action"] = "remove_card";
|
| 367 |
msg["model_type"] = model_type;
|
| 368 |
msg["search_term"] = search_term;
|
|
|
|
|
|
|
| 369 |
|
| 370 |
// fill to msg box
|
| 371 |
send_ch_py_msg(msg)
|
|
|
|
| 373 |
//click hidden button
|
| 374 |
js_remove_card_btn.click();
|
| 375 |
|
| 376 |
+
//check response msg from python
|
| 377 |
+
let new_py_msg = "";
|
| 378 |
+
try {
|
| 379 |
+
new_py_msg = await get_new_ch_py_msg();
|
| 380 |
+
} catch (error) {
|
| 381 |
+
console.log(error);
|
| 382 |
+
new_py_msg = error;
|
| 383 |
+
}
|
| 384 |
+
|
| 385 |
+
console.log("new_py_msg:");
|
| 386 |
+
console.log(new_py_msg);
|
| 387 |
+
|
| 388 |
+
//check msg
|
| 389 |
+
let result = "Done";
|
| 390 |
+
//check msg
|
| 391 |
+
if (new_py_msg) {
|
| 392 |
+
result = new_py_msg;
|
| 393 |
+
}
|
| 394 |
+
|
| 395 |
+
if (result == "Done") {
|
| 396 |
+
status = true;
|
| 397 |
+
refresh_cards_list(model_type);
|
| 398 |
+
}
|
| 399 |
+
|
| 400 |
+
console.log("end remove_card");
|
| 401 |
+
|
| 402 |
+
return status;
|
| 403 |
+
|
| 404 |
+
}
|
| 405 |
+
|
| 406 |
+
|
| 407 |
+
async function rename_card(event, model_type, search_term, model_name) {
|
| 408 |
+
console.log("start rename_card");
|
| 409 |
+
|
| 410 |
// stop parent event
|
| 411 |
event.stopPropagation()
|
| 412 |
event.preventDefault()
|
| 413 |
|
| 414 |
+
//get hidden components of extension
|
| 415 |
+
let js_rename_card_btn = gradioApp().getElementById("ch_js_rename_card_btn");
|
| 416 |
+
if (!js_rename_card_btn) {
|
| 417 |
+
return;
|
| 418 |
+
}
|
| 419 |
+
|
| 420 |
+
// must confirm before removing
|
| 421 |
+
let rename_prompt = "\nRename this model to:";
|
| 422 |
+
let new_name = prompt(rename_prompt, model_name);
|
| 423 |
+
if (!new_name) {
|
| 424 |
+
return;
|
| 425 |
+
}
|
| 426 |
+
|
| 427 |
+
//msg to python side
|
| 428 |
+
let msg = {
|
| 429 |
+
"action": "",
|
| 430 |
+
"model_type": "",
|
| 431 |
+
"search_term": "",
|
| 432 |
+
"new_name": "",
|
| 433 |
+
}
|
| 434 |
+
|
| 435 |
+
msg["action"] = "rename_card";
|
| 436 |
+
msg["model_type"] = model_type;
|
| 437 |
+
msg["search_term"] = search_term;
|
| 438 |
+
msg["new_name"] = new_name;
|
| 439 |
+
|
| 440 |
+
// fill to msg box
|
| 441 |
+
send_ch_py_msg(msg)
|
| 442 |
+
|
| 443 |
+
//click hidden button
|
| 444 |
+
js_rename_card_btn.click();
|
| 445 |
+
|
| 446 |
//check response msg from python
|
| 447 |
let new_py_msg = "";
|
| 448 |
try {
|
|
|
|
| 451 |
console.log(error);
|
| 452 |
new_py_msg = error;
|
| 453 |
}
|
| 454 |
+
|
| 455 |
console.log("new_py_msg:");
|
| 456 |
console.log(new_py_msg);
|
| 457 |
|
|
|
|
| 462 |
result = new_py_msg;
|
| 463 |
}
|
| 464 |
|
| 465 |
+
if (result == "Done") {
|
| 466 |
+
refresh_cards_list(model_type);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 467 |
}
|
| 468 |
+
|
| 469 |
+
console.log("end rename_card");
|
| 470 |
+
|
| 471 |
+
}
|
| 472 |
|
| 473 |
|
| 474 |
+
function replace_preview(e, page, type, name) {
|
| 475 |
+
// we have to create a whole hidden editor window to access preview replace functionality
|
| 476 |
+
extraNetworksEditUserMetadata(e, page, type, name);
|
| 477 |
+
|
| 478 |
+
// the editor window takes quite some time to populate
|
| 479 |
+
waitForEditor(page, type, name).then(editor => {
|
| 480 |
+
// Gather the buttons we need to both replace the preview and close the editor
|
| 481 |
+
let cancel_button = editor.querySelector('.edit-user-metadata-buttons button:first-of-type');
|
| 482 |
+
let replace_preview_button = editor.querySelector('.edit-user-metadata-buttons button:nth-of-type(2)');
|
| 483 |
+
|
| 484 |
+
replace_preview_button.click();
|
| 485 |
+
cancel_button.click();
|
| 486 |
+
});
|
| 487 |
}
|
| 488 |
|
| 489 |
|
| 490 |
// download model's new version into SD at python side
|
| 491 |
+
function ch_dl_model_new_version(event, model_path, version_id, download_url, model_type) {
|
| 492 |
console.log("start ch_dl_model_new_version");
|
| 493 |
|
| 494 |
+
event.stopPropagation();
|
| 495 |
+
event.preventDefault();
|
| 496 |
+
|
| 497 |
// must confirm before downloading
|
| 498 |
let dl_confirm = "\nConfirm to download.\n\nCheck Download Model Section's log and console log for detail.";
|
| 499 |
+
|
| 500 |
if (!confirm(dl_confirm)) {
|
| 501 |
+
return;
|
| 502 |
}
|
| 503 |
|
| 504 |
+
//get hidden components of extension
|
| 505 |
let js_dl_model_new_version_btn = gradioApp().getElementById("ch_js_dl_model_new_version_btn");
|
| 506 |
if (!js_dl_model_new_version_btn) {
|
| 507 |
+
return;
|
| 508 |
}
|
| 509 |
|
| 510 |
//msg to python side
|
|
|
|
| 513 |
"model_path": "",
|
| 514 |
"version_id": "",
|
| 515 |
"download_url": "",
|
| 516 |
+
};
|
| 517 |
|
| 518 |
msg["action"] = "dl_model_new_version";
|
| 519 |
msg["model_path"] = model_path;
|
| 520 |
msg["version_id"] = version_id;
|
| 521 |
msg["download_url"] = download_url;
|
| 522 |
+
msg["model_type"] = model_type;
|
| 523 |
|
| 524 |
// fill to msg box
|
| 525 |
+
send_ch_py_msg(msg);
|
| 526 |
|
| 527 |
//click hidden button
|
| 528 |
js_dl_model_new_version_btn.click();
|
| 529 |
|
| 530 |
console.log("end dl_model_new_version");
|
| 531 |
|
|
|
|
|
|
|
| 532 |
|
| 533 |
+
}
|
| 534 |
+
|
| 535 |
+
|
| 536 |
+
function refresh_cards_list(model_type) {
|
| 537 |
+
console.log("refresh card list");
|
| 538 |
+
//refresh card list
|
| 539 |
+
let active_tab = getActiveTabType();
|
| 540 |
+
console.log(`get active tab id: ${active_tab}`);
|
| 541 |
+
if (active_tab) {
|
| 542 |
+
let refresh_btn_id = `${active_tab}_extra_refresh`;
|
| 543 |
+
let refresh_btn = gradioApp().getElementById(refresh_btn_id);
|
| 544 |
+
if (!refresh_btn) {
|
| 545 |
+
// webui 1.8
|
| 546 |
+
refresh_btn_id = `${active_tab}_${model_type}_extra_refresh`;
|
| 547 |
+
refresh_btn = gradioApp().getElementById(refresh_btn_id);
|
| 548 |
+
}
|
| 549 |
+
if (refresh_btn) {
|
| 550 |
+
console.log(`click button: ${refresh_btn_id}`);
|
| 551 |
+
refresh_btn.click();
|
| 552 |
+
}
|
| 553 |
+
}
|
| 554 |
+
}
|
| 555 |
+
|
| 556 |
+
function processCards(tab, extra_tab_els) {
|
| 557 |
+
if (!(opts && "ch_always_display" in opts)) {
|
| 558 |
+
// Lobe theme can cause a race condition.
|
| 559 |
+
console.log("Waiting for webui settings to become available");
|
| 560 |
+
console.log(opts);
|
| 561 |
+
const try_again = function () {
|
| 562 |
+
processCards(tab, extra_tab_els);
|
| 563 |
+
}
|
| 564 |
+
return setTimeout(try_again, 500);
|
| 565 |
+
}
|
| 566 |
+
|
| 567 |
+
const prefix_length = tab.length + 1;
|
| 568 |
+
for (const el of extra_tab_els) {
|
| 569 |
+
const model_type = el.id.slice(prefix_length, -6);
|
| 570 |
+
const cards = el.querySelectorAll('.card');
|
| 571 |
+
for (const card of cards) {
|
| 572 |
+
processSingleCard(tab, getShortModelTypeFromFull(model_type), card);
|
| 573 |
+
}
|
| 574 |
+
}
|
| 575 |
+
}
|
| 576 |
+
|
| 577 |
+
|
| 578 |
+
function getModelCardsEl(prefix, model_type) {
|
| 579 |
+
const id = prefix + "_" + model_type + "_cards";
|
| 580 |
+
return gradioApp().getElementById(id);
|
| 581 |
+
}
|
| 582 |
+
|
| 583 |
+
|
| 584 |
+
function waitForExtraTabs(tab, extra_tabs) {
|
| 585 |
+
function findTabs() {
|
| 586 |
+
const tab_elements = [];
|
| 587 |
+
for (const extra_tab of extra_tabs) {
|
| 588 |
+
const extra_tab_el = getModelCardsEl(tab, extra_tab);
|
| 589 |
+
|
| 590 |
+
if (extra_tab_el == null) {
|
| 591 |
+
|
| 592 |
+
// XXX lycoris models do not have their own tab in sdwebui 1.5
|
| 593 |
+
// most of the time. In the case that there is a LyCoris tab,
|
| 594 |
+
// it would have been added at the same time as the others,
|
| 595 |
+
// making it almost impossible to be null by the time we're at
|
| 596 |
+
// this point in the code if the other tabs are loaded.
|
| 597 |
+
if (extra_tab == 'lycoris') { continue; }
|
| 598 |
+
|
| 599 |
+
return null;
|
| 600 |
+
}
|
| 601 |
+
|
| 602 |
+
tab_elements.push(extra_tab_el);
|
| 603 |
+
}
|
| 604 |
+
return tab_elements;
|
| 605 |
+
}
|
| 606 |
+
|
| 607 |
+
const tab_elements = findTabs(tab, extra_tabs);
|
| 608 |
+
if (tab_elements) {
|
| 609 |
+
processCards(tab, tab_elements);
|
| 610 |
+
}
|
| 611 |
+
|
| 612 |
+
const observer = new MutationObserver(records => {
|
| 613 |
+
let tab_elements;
|
| 614 |
+
for (const record of records) {
|
| 615 |
+
if (record.type != "childList") {
|
| 616 |
+
continue;
|
| 617 |
+
}
|
| 618 |
+
|
| 619 |
+
tab_elements = findTabs(tab, extra_tabs);
|
| 620 |
+
if (!tab_elements) {
|
| 621 |
+
return;
|
| 622 |
+
}
|
| 623 |
+
|
| 624 |
+
processCards(tab, tab_elements);
|
| 625 |
+
return;
|
| 626 |
+
}
|
| 627 |
+
});
|
| 628 |
+
|
| 629 |
+
const extra_networks = getExtraTabs(tab);
|
| 630 |
+
|
| 631 |
+
const options = {
|
| 632 |
+
subtree: true,
|
| 633 |
+
childList: true,
|
| 634 |
+
};
|
| 635 |
+
|
| 636 |
+
observer.observe(extra_networks, options);
|
| 637 |
+
|
| 638 |
+
}
|
| 639 |
+
|
| 640 |
+
|
| 641 |
+
function waitForEditor(page, type, name) {
|
| 642 |
+
const id = page + '_' + type + '_edit_user_metadata';
|
| 643 |
+
|
| 644 |
+
return new Promise(resolve => {
|
| 645 |
+
let name_field;
|
| 646 |
+
const gradio = gradioApp();
|
| 647 |
|
| 648 |
+
const editor = gradio.getElementById(id);
|
| 649 |
+
const popup = gradio.querySelector(".global-popup");
|
| 650 |
+
|
| 651 |
+
if (popup != null) {
|
| 652 |
+
// hide the editor window so it doesn't get in the user's
|
| 653 |
+
// way while we wait for the replace preview functionality
|
| 654 |
+
// to become available.
|
| 655 |
+
popup.style.display = "none";
|
| 656 |
+
}
|
| 657 |
+
|
| 658 |
+
// not only do we need to wait for the editor,
|
| 659 |
+
// but also for it to populate with the model metadata.
|
| 660 |
+
if (editor != null) {
|
| 661 |
+
name_field = editor.querySelector('.extra-network-name');
|
| 662 |
+
if (name_field.textContent.trim() == name) {
|
| 663 |
+
return resolve(editor);
|
| 664 |
+
}
|
| 665 |
+
}
|
| 666 |
+
|
| 667 |
+
const observer = new MutationObserver(() => {
|
| 668 |
+
const editor = gradioApp().getElementById(id);
|
| 669 |
+
let name_field;
|
| 670 |
+
if (editor != null) {
|
| 671 |
+
name_field = editor.querySelector('.extra-network-name');
|
| 672 |
+
if (name_field.textContent.trim() == name) {
|
| 673 |
+
resolve(editor);
|
| 674 |
+
observer.disconnect();
|
| 675 |
+
}
|
| 676 |
+
}
|
| 677 |
+
});
|
| 678 |
+
|
| 679 |
+
observer.observe(document.body, {
|
| 680 |
+
subtree: true,
|
| 681 |
+
childList: true,
|
| 682 |
+
});
|
| 683 |
+
});
|
| 684 |
}
|
| 685 |
|
| 686 |
|
| 687 |
+
function getShortModelTypeFromFull(model_type_full) {
|
| 688 |
+
switch (model_type_full) {
|
| 689 |
+
case "textual_inversion":
|
| 690 |
+
return "ti";
|
| 691 |
+
case "hypernetworks":
|
| 692 |
+
return "hyper";
|
| 693 |
+
case "checkpoints":
|
| 694 |
+
return "ckp";
|
| 695 |
+
case "lora":
|
| 696 |
+
case "lycoris":
|
| 697 |
+
return model_type_full;
|
| 698 |
+
}
|
| 699 |
+
}
|
| 700 |
+
|
| 701 |
+
|
| 702 |
+
function getLongModelTypeFromShort(model_type_short) {
|
| 703 |
+
switch (model_type_short) {
|
| 704 |
+
case "ti":
|
| 705 |
+
return "textual_inversion";
|
| 706 |
+
case "hyper":
|
| 707 |
+
return "hypernetworks";
|
| 708 |
+
case "ckp":
|
| 709 |
+
return "checkpoints";
|
| 710 |
+
case "lora":
|
| 711 |
+
case "lycoris":
|
| 712 |
+
return model_type_short;
|
| 713 |
+
}
|
| 714 |
+
}
|
| 715 |
+
|
| 716 |
+
|
| 717 |
+
function isThumbMode(extra_network_node) {
|
| 718 |
+
if (extra_network_node?.className == "extra-network-thumbs") {
|
| 719 |
+
return true;
|
| 720 |
+
}
|
| 721 |
+
return false;
|
| 722 |
+
}
|
| 723 |
+
|
| 724 |
+
|
| 725 |
+
function processSingleCard(active_tab_type, active_extra_tab_type, card) {
|
| 726 |
+
let metadata_button = null;
|
| 727 |
+
let additional_node = null;
|
| 728 |
+
let replace_preview_btn = null;
|
| 729 |
+
let ul_node = null;
|
| 730 |
+
let model_name = "";
|
| 731 |
+
let search_term = "";
|
| 732 |
+
let model_type = active_extra_tab_type;
|
| 733 |
+
let js_model_type = getLongModelTypeFromShort(model_type);
|
| 734 |
+
let addedNodes = [];
|
| 735 |
+
|
| 736 |
+
let is_thumb_mode = isThumbMode(getModelCardsEl(active_tab_type, js_model_type));
|
| 737 |
+
|
| 738 |
+
//metadata_buttoncard
|
| 739 |
+
metadata_button = card.querySelector(".metadata-button");
|
| 740 |
+
//additional node
|
| 741 |
+
additional_node = card.querySelector(".actions .additional");
|
| 742 |
+
//get ul node, which is the parent of all buttons
|
| 743 |
+
ul_node = card.querySelector(".actions .additional ul");
|
| 744 |
+
|
| 745 |
+
// check thumb mode
|
| 746 |
+
if (is_thumb_mode) {
|
| 747 |
+
additional_node.style.display = null;
|
| 748 |
+
|
| 749 |
+
if (!ul_node) {
|
| 750 |
+
// nothing to do.
|
| 751 |
+
return;
|
| 752 |
+
}
|
| 753 |
+
|
| 754 |
+
if (opts["ch_show_btn_on_thumb"]) {
|
| 755 |
+
ul_node.style.background = btn_thumb_background;
|
| 756 |
+
} else {
|
| 757 |
+
let ch_btn_txts = ["💡", "🌐", "🏷️", "✏️", "❌"];
|
| 758 |
+
|
| 759 |
+
// remove existed buttons
|
| 760 |
+
//reset
|
| 761 |
+
ul_node.style.background = null;
|
| 762 |
+
// find all .a child nodes
|
| 763 |
+
let atags = ul_node.querySelectorAll("a");
|
| 764 |
+
|
| 765 |
+
for (let atag of atags) {
|
| 766 |
+
//reset display
|
| 767 |
+
atag.style.display = null;
|
| 768 |
+
//remove extension's button
|
| 769 |
+
if (ch_btn_txts.indexOf(atag.textContent)>=0) {
|
| 770 |
+
//need to remove
|
| 771 |
+
ul_node.removeChild(atag);
|
| 772 |
+
} else {
|
| 773 |
+
//do not remove, just reset
|
| 774 |
+
atag.textContent = replace_preview_text;
|
| 775 |
+
atag.style.display = null;
|
| 776 |
+
atag.style.fontSize = null;
|
| 777 |
+
atag.style.position = null;
|
| 778 |
+
atag.style.backgroundImage = null;
|
| 779 |
+
}
|
| 780 |
+
}
|
| 781 |
+
|
| 782 |
+
//also remove br tag in ul
|
| 783 |
+
let brtag = ul_node.querySelector("br");
|
| 784 |
+
if (brtag) {
|
| 785 |
+
ul_node.removeChild(brtag);
|
| 786 |
+
}
|
| 787 |
+
|
| 788 |
+
//just reset and remove nodes, do nothing else
|
| 789 |
+
return;
|
| 790 |
+
|
| 791 |
+
}
|
| 792 |
+
|
| 793 |
+
} else {
|
| 794 |
+
// full preview mode
|
| 795 |
+
|
| 796 |
+
if (opts["ch_always_display"]) {
|
| 797 |
+
additional_node.style.display = "block";
|
| 798 |
+
} else {
|
| 799 |
+
additional_node.style.display = null;
|
| 800 |
+
}
|
| 801 |
+
|
| 802 |
+
if (!ul_node) {
|
| 803 |
+
ul_node = document.createElement("ul");
|
| 804 |
+
} else {
|
| 805 |
+
// remove br tag
|
| 806 |
+
let brtag = ul_node.querySelector("br");
|
| 807 |
+
if (brtag) {
|
| 808 |
+
ul_node.removeChild(brtag);
|
| 809 |
+
}
|
| 810 |
+
}
|
| 811 |
+
|
| 812 |
+
}
|
| 813 |
+
|
| 814 |
+
if (ul_node.dataset.ch_helper) {
|
| 815 |
+
return;
|
| 816 |
+
}
|
| 817 |
+
|
| 818 |
+
ul_node.dataset.ch_helper = true;
|
| 819 |
+
|
| 820 |
+
model_name = card.dataset.name;
|
| 821 |
+
|
| 822 |
+
// replace preview text button
|
| 823 |
+
replace_preview_btn = card.querySelector(".actions .additional a");
|
| 824 |
+
|
| 825 |
+
if ((replace_preview_btn == null) && !("replace_preview_button" in opts["ch_hide_buttons"])) {
|
| 826 |
+
/*
|
| 827 |
+
* in sdwebui 1.5, the replace preview button has been
|
| 828 |
+
* moved to a hard to reach location, so we have to do
|
| 829 |
+
* quite a lot to get to its functionality.
|
| 830 |
+
*/
|
| 831 |
+
|
| 832 |
+
// waste memory by keeping all of this in scope, per card.
|
| 833 |
+
let page = active_tab_type;
|
| 834 |
+
let type = js_model_type;
|
| 835 |
+
let name = card.dataset.name.replace("'", "\\'");
|
| 836 |
+
|
| 837 |
+
// create the replace_preview_btn, as it no longer exists
|
| 838 |
+
replace_preview_btn = document.createElement("a");
|
| 839 |
+
|
| 840 |
+
// create an event handler to redirect a click to the real replace_preview_button
|
| 841 |
+
replace_preview_btn.setAttribute("onclick", `replace_preview(event, '${page}', '${type}', '${model_name}')`);
|
| 842 |
+
}
|
| 843 |
+
|
| 844 |
+
// change replace preview text button into icon
|
| 845 |
+
if (!opts["ch_hide_buttons"].includes("replace_preview_button")) {
|
| 846 |
+
if (replace_preview_btn.textContent !== "🖼️") {
|
| 847 |
+
replace_preview_btn.textContent = "🖼️";
|
| 848 |
+
addedNodes.push(replace_preview_btn);
|
| 849 |
+
}
|
| 850 |
+
|
| 851 |
+
replace_preview_btn.classList.add("card-button", "removecard");
|
| 852 |
+
|
| 853 |
+
} else if (replace_preview_btn.parentElement) {
|
| 854 |
+
replace_preview_btn.parentElement.removeChild(replace_preview_btn);
|
| 855 |
+
}
|
| 856 |
+
|
| 857 |
+
// search_term node
|
| 858 |
+
// search_term: /[subfolder path]/[model name].[ext] [hash]
|
| 859 |
+
// get search_term
|
| 860 |
+
let search_term_nodes = card.querySelectorAll(".actions .additional .search_term, .actions .additional .search_terms");
|
| 861 |
+
if (!search_term_nodes) {
|
| 862 |
+
console.log("can not find search_term node for cards in " + active_tab_type + "_" + active_extra_tab_type + "_cards");
|
| 863 |
+
return;
|
| 864 |
+
}
|
| 865 |
+
|
| 866 |
+
if (search_term_nodes.length > 1) {
|
| 867 |
+
let search_terms = [];
|
| 868 |
+
for (let search_term_node of search_term_nodes) {
|
| 869 |
+
search_terms.push(search_term_node.textContent);
|
| 870 |
+
}
|
| 871 |
+
|
| 872 |
+
let model_path = search_terms.join(" ");
|
| 873 |
+
let separator = model_path.match(/[\/\\]/)[0];
|
| 874 |
+
model_path = model_path.split(separator).slice(1).join(separator);
|
| 875 |
+
|
| 876 |
+
search_term = model_path;
|
| 877 |
+
} else {
|
| 878 |
+
let search_term_node = search_term_nodes[0];
|
| 879 |
+
search_term = search_term_node.textContent;
|
| 880 |
+
|
| 881 |
+
// for whatever reason, sometimes search_terms will not include hashes.
|
| 882 |
+
if (search_term_node.classList.contains("search_terms")) {
|
| 883 |
+
let separator = search_term.match(/[\/\\]/)[0];
|
| 884 |
+
search_term = search_term.split(separator).slice(1).join(
|
| 885 |
+
separator === "\\" ? "\\\\" : "/"
|
| 886 |
+
);
|
| 887 |
+
}
|
| 888 |
+
}
|
| 889 |
+
|
| 890 |
+
search_term = search_term.replaceAll("\\", "\\\\").replace("'", "\\'");
|
| 891 |
+
|
| 892 |
+
if (!search_term) {
|
| 893 |
+
console.log("search_term is empty for cards in " + active_tab_type + "_" + active_extra_tab_type + "_cards");
|
| 894 |
+
return;
|
| 895 |
+
}
|
| 896 |
+
|
| 897 |
+
// then we need to add buttons to each ul node:
|
| 898 |
+
if (!opts["ch_hide_buttons"].includes("open_url_button")) {
|
| 899 |
+
let open_url_node = document.createElement("a");
|
| 900 |
+
open_url_node.href = "#";
|
| 901 |
+
open_url_node.textContent = "🌐";
|
| 902 |
+
open_url_node.classList.add("card-button", "openurl");
|
| 903 |
+
open_url_node.title = "Open this model's civitai url";
|
| 904 |
+
open_url_node.setAttribute("onclick", `open_model_url(event, '${model_type}', '${search_term}')`);
|
| 905 |
+
addedNodes.push(open_url_node);
|
| 906 |
+
}
|
| 907 |
+
|
| 908 |
+
// add br if metadata_button exists
|
| 909 |
+
if (is_thumb_mode && metadata_button) {
|
| 910 |
+
addedNodes.push(document.createElement("br"));
|
| 911 |
+
}
|
| 912 |
+
|
| 913 |
+
if (!opts["ch_hide_buttons"].includes("add_trigger_words_button")) {
|
| 914 |
+
let add_trigger_words_node = document.createElement("a");
|
| 915 |
+
add_trigger_words_node.href = "#";
|
| 916 |
+
add_trigger_words_node.textContent = "💡";
|
| 917 |
+
add_trigger_words_node.classList.add("card-button", "addtriggerwords");
|
| 918 |
+
add_trigger_words_node.title = "Add trigger words to prompt";
|
| 919 |
+
add_trigger_words_node.setAttribute("onclick", `add_trigger_words(event, '${model_type}', '${search_term}')`);
|
| 920 |
+
addedNodes.push(add_trigger_words_node);
|
| 921 |
+
}
|
| 922 |
+
|
| 923 |
+
if (!opts["ch_hide_buttons"].includes("add_preview_prompt_button")) {
|
| 924 |
+
let use_preview_prompt_node = document.createElement("a");
|
| 925 |
+
use_preview_prompt_node.href = "#";
|
| 926 |
+
use_preview_prompt_node.textContent = "🏷️";
|
| 927 |
+
use_preview_prompt_node.classList.add("card-button", "usepreviewprompt");
|
| 928 |
+
use_preview_prompt_node.title = "Use prompt from preview image";
|
| 929 |
+
use_preview_prompt_node.setAttribute("onclick", `use_preview_prompt(event, '${model_type}', '${search_term}')`);
|
| 930 |
+
addedNodes.push(use_preview_prompt_node);
|
| 931 |
+
}
|
| 932 |
+
|
| 933 |
+
if (!opts["ch_hide_buttons"].includes("rename_model_button")) {
|
| 934 |
+
let rename_card_node = document.createElement("a");
|
| 935 |
+
rename_card_node.href = "#";
|
| 936 |
+
rename_card_node.innerHTML = "✏️";
|
| 937 |
+
rename_card_node.classList.add("card-button", "renamecard");
|
| 938 |
+
rename_card_node.title = "Rename this model";
|
| 939 |
+
rename_card_node.setAttribute("onclick", `rename_card(event, '${model_type}', '${search_term}', '${model_name}')`);
|
| 940 |
+
addedNodes.push(rename_card_node);
|
| 941 |
+
}
|
| 942 |
+
|
| 943 |
+
if (!opts["ch_hide_buttons"].includes("remove_model_button")) {
|
| 944 |
+
let remove_card_node = document.createElement("a");
|
| 945 |
+
remove_card_node.href = "#";
|
| 946 |
+
remove_card_node.innerHTML = "❌";
|
| 947 |
+
remove_card_node.classList.add("card-button", "removecard");
|
| 948 |
+
remove_card_node.title = "Remove this model";
|
| 949 |
+
remove_card_node.setAttribute("onclick", `remove_card(event, '${model_type}', '${search_term}')`);
|
| 950 |
+
addedNodes.push(remove_card_node);
|
| 951 |
+
}
|
| 952 |
+
|
| 953 |
+
// add to buttons row
|
| 954 |
+
for (const node of addedNodes) {
|
| 955 |
+
ul_node.appendChild(node);
|
| 956 |
+
}
|
| 957 |
+
|
| 958 |
+
// add buttons to card
|
| 959 |
+
if (!ul_node.parentElement && ul_node.children) {
|
| 960 |
+
additional_node.appendChild(ul_node);
|
| 961 |
+
}
|
| 962 |
+
}
|
| 963 |
+
|
| 964 |
onUiLoaded(() => {
|
| 965 |
|
| 966 |
//get gradio version
|
| 967 |
+
const gradio_ver = ch_gradio_version();
|
| 968 |
+
console.log("Running Stable-Diffusion-Webui-Civitai-Helper on Gradio Version: " + gradio_ver);
|
| 969 |
|
| 970 |
// get all extra network tabs
|
| 971 |
+
const tab_prefix_list = ["txt2img", "img2img"];
|
| 972 |
+
const model_type_list = ["textual_inversion", "hypernetworks", "checkpoints", "lora", "lycoris"];
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 973 |
|
| 974 |
// update extra network tab pages' cards
|
| 975 |
// * replace "replace preview" text button into an icon
|
|
|
|
| 980 |
// notice: javascript can not get response from python side
|
| 981 |
// so, these buttons just sent request to python
|
| 982 |
// then, python side gonna open url and update prompt text box, without telling js side.
|
| 983 |
+
function update_card_for_civitai() {
|
| 984 |
+
if (!(opts && "ch_always_display" in opts)) {
|
| 985 |
+
// Lobe theme can cause a race condition.
|
| 986 |
+
return setTimeout(update_card_for_civitai, 500);
|
| 987 |
+
}
|
| 988 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 989 |
let replace_preview_text = getTranslation("replace preview");
|
| 990 |
if (!replace_preview_text) {
|
| 991 |
replace_preview_text = "replace preview";
|
| 992 |
}
|
| 993 |
|
|
|
|
|
|
|
|
|
|
| 994 |
let extra_network_node = null;
|
|
|
|
|
|
|
|
|
|
| 995 |
let model_type = "";
|
| 996 |
let cards = null;
|
|
|
|
| 997 |
|
| 998 |
//get current tab
|
| 999 |
let active_tab_type = getActiveTabType();
|
| 1000 |
+
if (!active_tab_type) {active_tab_type = "txt2img";}
|
| 1001 |
|
| 1002 |
for (const tab_prefix of tab_prefix_list) {
|
| 1003 |
if (tab_prefix != active_tab_type) {continue;}
|
| 1004 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1005 |
//get active extratab
|
| 1006 |
+
const re = new RegExp(tab_prefix + "_(.+)_cards");
|
| 1007 |
const active_extra_tab = Array.from(get_uiCurrentTabContent().querySelectorAll('.extra-network-cards,.extra-network-thumbs'))
|
| 1008 |
.find(el => el.closest('.tabitem').style.display === 'block')
|
| 1009 |
+
?.id.match(re)[1];
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1010 |
|
| 1011 |
+
const active_extra_tab_type = getShortModelTypeFromFull(active_extra_tab);
|
| 1012 |
|
| 1013 |
for (const js_model_type of model_type_list) {
|
| 1014 |
//get model_type for python side
|
| 1015 |
+
model_type = getShortModelTypeFromFull(js_model_type);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1016 |
|
| 1017 |
if (!model_type) {
|
| 1018 |
console.log("can not get model_type from: " + js_model_type);
|
|
|
|
| 1025 |
continue;
|
| 1026 |
}
|
| 1027 |
|
| 1028 |
+
//console.log("handle active extra tab");
|
| 1029 |
+
extra_network_id = tab_prefix + "_" + js_model_type + "_cards";
|
| 1030 |
|
|
|
|
| 1031 |
// console.log("searching extra_network_node: " + extra_network_id);
|
| 1032 |
+
extra_network_node = getModelCardsEl(tab_prefix, js_model_type);
|
|
|
|
|
|
|
| 1033 |
|
| 1034 |
// get all card nodes
|
| 1035 |
cards = extra_network_node.querySelectorAll(".card");
|
| 1036 |
+
for (const card of cards) {
|
| 1037 |
+
// don't let an issue with a single card kill functionality for following cards
|
| 1038 |
+
try {
|
| 1039 |
+
processSingleCard(active_tab_type, active_extra_tab_type, card);
|
| 1040 |
+
} catch(err) {
|
| 1041 |
+
console.log(err);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1042 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1043 |
}
|
| 1044 |
|
|
|
|
| 1045 |
}
|
|
|
|
| 1046 |
|
| 1047 |
+
}
|
| 1048 |
|
| 1049 |
}
|
| 1050 |
|
| 1051 |
+
/*
|
|
|
|
|
|
|
|
|
|
| 1052 |
let extra_network_refresh_btn = null;
|
| 1053 |
+
*/
|
| 1054 |
+
let extra_networks_btn = null;
|
| 1055 |
+
|
| 1056 |
//add refresh button to extra network's toolbar
|
| 1057 |
+
for (const prefix of tab_prefix_list) {
|
| 1058 |
+
// load extra networks button
|
| 1059 |
+
extra_networks_btn = gradioApp().getElementById(prefix + "_extra_networks");
|
| 1060 |
|
|
|
|
|
|
|
|
|
|
| 1061 |
|
| 1062 |
+
// pre-1.6
|
| 1063 |
+
if (extra_networks_btn) {
|
| 1064 |
+
function extraNetworksClick() {
|
| 1065 |
+
waitForExtraTabs(prefix, model_type_list);
|
| 1066 |
+
extra_networks_btn.removeEventListener("click", extraNetworksClick);
|
| 1067 |
+
}
|
| 1068 |
|
| 1069 |
+
// add listener to extra_networks_btn
|
| 1070 |
+
extra_networks_btn.addEventListener("click", extraNetworksClick);
|
| 1071 |
continue;
|
| 1072 |
+
|
| 1073 |
}
|
| 1074 |
|
| 1075 |
+
// 1.6 and higher
|
| 1076 |
+
const extra_tab = getExtraTabs(prefix);
|
| 1077 |
+
const headers = extra_tab.firstChild.children;
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1078 |
|
| 1079 |
+
for (const header of headers) {
|
| 1080 |
+
const model_type = header.textContent.trim().replace(" ", "_").toLowerCase();
|
| 1081 |
|
| 1082 |
+
function extraNetworksClick() {
|
| 1083 |
+
waitForExtraTabs(prefix, [model_type]);
|
| 1084 |
+
header.removeEventListener("click", extraNetworksClick);
|
| 1085 |
+
}
|
| 1086 |
|
| 1087 |
+
header.addEventListener("click", extraNetworksClick);
|
| 1088 |
+
}
|
| 1089 |
+
|
| 1090 |
+
//get toolbar
|
| 1091 |
+
extra_networks_btn = gradioApp().getElementById(prefix + "_extra_networks");
|
| 1092 |
+
|
| 1093 |
+
}
|
| 1094 |
|
| 1095 |
//run it once
|
| 1096 |
+
// update_card_for_civitai();
|
| 1097 |
|
| 1098 |
|
| 1099 |
});
|
extensions/Stable-Diffusion-Webui-Civitai-Helper/scripts/__pycache__/civitai_helper.cpython-310.pyc
DELETED
|
Binary file (8.32 kB)
|
|
|
extensions/Stable-Diffusion-Webui-Civitai-Helper/scripts/ch_lib/__pycache__/__init__.cpython-310.pyc
DELETED
|
Binary file (186 Bytes)
|
|
|
extensions/Stable-Diffusion-Webui-Civitai-Helper/scripts/ch_lib/__pycache__/civitai.cpython-310.pyc
DELETED
|
Binary file (9.31 kB)
|
|
|
extensions/Stable-Diffusion-Webui-Civitai-Helper/scripts/ch_lib/__pycache__/downloader.cpython-310.pyc
DELETED
|
Binary file (2.58 kB)
|
|
|
extensions/Stable-Diffusion-Webui-Civitai-Helper/scripts/ch_lib/__pycache__/js_action_civitai.cpython-310.pyc
DELETED
|
Binary file (5.57 kB)
|
|
|
extensions/Stable-Diffusion-Webui-Civitai-Helper/scripts/ch_lib/__pycache__/model.cpython-310.pyc
DELETED
|
Binary file (3.52 kB)
|
|
|
extensions/Stable-Diffusion-Webui-Civitai-Helper/scripts/ch_lib/__pycache__/model_action_civitai.cpython-310.pyc
DELETED
|
Binary file (8.57 kB)
|
|
|
extensions/Stable-Diffusion-Webui-Civitai-Helper/scripts/ch_lib/__pycache__/msg_handler.cpython-310.pyc
DELETED
|
Binary file (1.22 kB)
|
|
|
extensions/Stable-Diffusion-Webui-Civitai-Helper/scripts/ch_lib/__pycache__/setting.cpython-310.pyc
DELETED
|
Binary file (1.64 kB)
|
|
|
extensions/Stable-Diffusion-Webui-Civitai-Helper/scripts/ch_lib/__pycache__/util.cpython-310.pyc
DELETED
|
Binary file (2.68 kB)
|
|
|
extensions/Stable-Diffusion-Webui-Civitai-Helper/scripts/ch_lib/civitai.py
CHANGED
|
@@ -1,617 +1,726 @@
|
|
| 1 |
-
|
| 2 |
-
|
|
|
|
|
|
|
| 3 |
import os
|
| 4 |
-
import time
|
| 5 |
-
import json
|
| 6 |
import re
|
| 7 |
-
import requests
|
| 8 |
from . import util
|
| 9 |
from . import model
|
| 10 |
-
from . import
|
| 11 |
|
| 12 |
-
|
| 13 |
|
| 14 |
-
|
| 15 |
"modelPage":"https://civitai.com/models/",
|
| 16 |
"modelId": "https://civitai.com/api/v1/models/",
|
| 17 |
"modelVersionId": "https://civitai.com/api/v1/model-versions/",
|
| 18 |
"hash": "https://civitai.com/api/v1/model-versions/by-hash/"
|
| 19 |
}
|
| 20 |
|
| 21 |
-
|
| 22 |
"Checkpoint": "ckp",
|
| 23 |
"TextualInversion": "ti",
|
| 24 |
"Hypernetwork": "hyper",
|
| 25 |
"LORA": "lora",
|
| 26 |
-
"LoCon": "
|
| 27 |
}
|
| 28 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 29 |
|
|
|
|
|
|
|
| 30 |
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 36 |
|
| 37 |
|
| 38 |
# use this sha256 to get model info from civitai
|
| 39 |
# return: model info dict
|
| 40 |
-
def get_model_info_by_hash(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 41 |
util.printD("Request model info from civitai")
|
| 42 |
|
| 43 |
-
if not
|
| 44 |
util.printD("hash is empty")
|
| 45 |
-
return
|
| 46 |
|
| 47 |
-
|
| 48 |
-
if not r.ok:
|
| 49 |
-
if r.status_code == 404:
|
| 50 |
-
# this is not a civitai model
|
| 51 |
-
util.printD("Civitai does not have this model")
|
| 52 |
-
return {}
|
| 53 |
-
else:
|
| 54 |
-
util.printD("Get error code: " + str(r.status_code))
|
| 55 |
-
util.printD(r.text)
|
| 56 |
-
return
|
| 57 |
|
| 58 |
-
# try to get content
|
| 59 |
-
content = None
|
| 60 |
-
try:
|
| 61 |
-
content = r.json()
|
| 62 |
-
except Exception as e:
|
| 63 |
-
util.printD("Parse response json failed")
|
| 64 |
-
util.printD(str(e))
|
| 65 |
-
util.printD("response:")
|
| 66 |
-
util.printD(r.text)
|
| 67 |
-
return
|
| 68 |
-
|
| 69 |
if not content:
|
| 70 |
-
|
| 71 |
-
|
| 72 |
-
|
|
|
|
| 73 |
return content
|
| 74 |
|
| 75 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 76 |
|
| 77 |
-
|
| 78 |
-
util.printD("Request model info from civitai")
|
| 79 |
|
| 80 |
-
if not
|
| 81 |
-
util.printD("
|
| 82 |
-
return
|
| 83 |
|
| 84 |
-
|
| 85 |
-
if not r.ok:
|
| 86 |
-
if r.status_code == 404:
|
| 87 |
-
# this is not a civitai model
|
| 88 |
-
util.printD("Civitai does not have this model")
|
| 89 |
-
return {}
|
| 90 |
-
else:
|
| 91 |
-
util.printD("Get error code: " + str(r.status_code))
|
| 92 |
-
util.printD(r.text)
|
| 93 |
-
return
|
| 94 |
|
| 95 |
-
# try to get content
|
| 96 |
-
content = None
|
| 97 |
-
try:
|
| 98 |
-
content = r.json()
|
| 99 |
-
except Exception as e:
|
| 100 |
-
util.printD("Parse response json failed")
|
| 101 |
-
util.printD(str(e))
|
| 102 |
-
util.printD("response:")
|
| 103 |
-
util.printD(r.text)
|
| 104 |
-
return
|
| 105 |
-
|
| 106 |
-
if not content:
|
| 107 |
-
util.printD("error, content from civitai is None")
|
| 108 |
-
return
|
| 109 |
-
|
| 110 |
return content
|
| 111 |
|
| 112 |
|
| 113 |
-
def get_version_info_by_version_id(
|
|
|
|
|
|
|
|
|
|
|
|
|
| 114 |
util.printD("Request version info from civitai")
|
| 115 |
|
| 116 |
-
if not
|
| 117 |
-
util.printD("
|
| 118 |
-
return
|
| 119 |
|
| 120 |
-
|
| 121 |
-
|
| 122 |
-
|
| 123 |
-
|
| 124 |
-
util.printD("Civitai does not have this model version")
|
| 125 |
-
return {}
|
| 126 |
-
else:
|
| 127 |
-
util.printD("Get error code: " + str(r.status_code))
|
| 128 |
-
util.printD(r.text)
|
| 129 |
-
return
|
| 130 |
|
| 131 |
-
# try to get content
|
| 132 |
-
content = None
|
| 133 |
-
try:
|
| 134 |
-
content = r.json()
|
| 135 |
-
except Exception as e:
|
| 136 |
-
util.printD("Parse response json failed")
|
| 137 |
-
util.printD(str(e))
|
| 138 |
-
util.printD("response:")
|
| 139 |
-
util.printD(r.text)
|
| 140 |
-
return
|
| 141 |
-
|
| 142 |
-
if not content:
|
| 143 |
-
util.printD("error, content from civitai is None")
|
| 144 |
-
return
|
| 145 |
-
|
| 146 |
return content
|
| 147 |
|
| 148 |
|
| 149 |
-
def get_version_info_by_model_id(
|
|
|
|
|
|
|
|
|
|
|
|
|
| 150 |
|
| 151 |
-
model_info = get_model_info_by_id(
|
| 152 |
if not model_info:
|
| 153 |
-
util.printD(f"Failed to get model info by id: {
|
| 154 |
-
return
|
| 155 |
-
|
| 156 |
# check content to get version id
|
| 157 |
-
|
| 158 |
-
|
| 159 |
-
|
| 160 |
-
|
| 161 |
-
|
| 162 |
-
|
| 163 |
-
return
|
| 164 |
-
|
| 165 |
-
if len(model_info["modelVersions"])==0:
|
| 166 |
-
util.printD("modelVersions is Empty")
|
| 167 |
-
return
|
| 168 |
-
|
| 169 |
-
def_version = model_info["modelVersions"][0]
|
| 170 |
if not def_version:
|
| 171 |
util.printD("default version is None")
|
| 172 |
-
return
|
| 173 |
-
|
| 174 |
-
|
| 175 |
-
|
| 176 |
-
return
|
| 177 |
-
|
| 178 |
-
version_id = def_version["id"]
|
| 179 |
-
|
| 180 |
if not version_id:
|
| 181 |
-
util.printD("
|
| 182 |
-
return
|
| 183 |
|
| 184 |
# get version info
|
| 185 |
-
version_info = get_version_info_by_version_id(
|
| 186 |
if not version_info:
|
| 187 |
util.printD(f"Failed to get version info by version_id: {version_id}")
|
| 188 |
-
return
|
| 189 |
|
| 190 |
return version_info
|
| 191 |
|
| 192 |
|
| 193 |
-
|
| 194 |
-
|
| 195 |
-
# get model info file's content by model type and search_term
|
| 196 |
-
# parameter: model_type, search_term
|
| 197 |
-
# return: model_info
|
| 198 |
def load_model_info_by_search_term(model_type, search_term):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 199 |
util.printD(f"Load model info of {search_term} in {model_type}")
|
| 200 |
-
if
|
| 201 |
-
util.printD("
|
| 202 |
-
return
|
| 203 |
-
|
| 204 |
-
# search_term = subfolderpath
|
| 205 |
-
|
|
|
|
| 206 |
model_info_base = base
|
| 207 |
if base[:1] == "/":
|
| 208 |
model_info_base = base[1:]
|
| 209 |
|
| 210 |
-
|
| 211 |
-
|
| 212 |
-
|
|
|
|
| 213 |
|
| 214 |
-
|
| 215 |
-
|
| 216 |
-
|
| 217 |
-
|
| 218 |
-
|
|
|
|
|
|
|
|
|
|
| 219 |
|
|
|
|
|
|
|
|
|
|
| 220 |
|
|
|
|
| 221 |
|
| 222 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 223 |
|
| 224 |
-
|
| 225 |
-
|
| 226 |
-
|
| 227 |
-
|
| 228 |
-
def get_model_names_by_type_and_filter(model_type:str, filter:dict) -> list:
|
| 229 |
-
|
| 230 |
-
model_folder = model.folders[model_type]
|
| 231 |
|
| 232 |
-
# set
|
| 233 |
# only get models don't have a civitai info file
|
| 234 |
no_info_only = False
|
| 235 |
empty_info_only = False
|
| 236 |
|
| 237 |
-
if
|
| 238 |
-
|
| 239 |
-
|
| 240 |
-
if "empty_info_only" in filter.keys():
|
| 241 |
-
empty_info_only = filter["empty_info_only"]
|
| 242 |
-
|
| 243 |
-
|
| 244 |
|
| 245 |
# get information from filter
|
| 246 |
# only get those model names don't have a civitai model info file
|
| 247 |
model_names = []
|
| 248 |
-
for
|
| 249 |
-
for
|
| 250 |
-
|
| 251 |
-
|
| 252 |
-
|
| 253 |
-
if ext in model.exts:
|
| 254 |
-
# find a model
|
| 255 |
-
|
| 256 |
-
# check filter
|
| 257 |
-
if no_info_only:
|
| 258 |
-
# check model info file
|
| 259 |
-
info_file = base + suffix + model.info_ext
|
| 260 |
-
if os.path.isfile(info_file):
|
| 261 |
-
continue
|
| 262 |
-
|
| 263 |
-
if empty_info_only:
|
| 264 |
-
# check model info file
|
| 265 |
-
info_file = base + suffix + model.info_ext
|
| 266 |
-
if os.path.isfile(info_file):
|
| 267 |
-
# load model info
|
| 268 |
-
model_info = model.load_model_info(info_file)
|
| 269 |
-
# check content
|
| 270 |
-
if model_info:
|
| 271 |
-
if "id" in model_info.keys():
|
| 272 |
-
# find a non-empty model info file
|
| 273 |
-
continue
|
| 274 |
-
|
| 275 |
-
model_names.append(filename)
|
| 276 |
-
|
| 277 |
|
| 278 |
return model_names
|
| 279 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 280 |
def get_model_names_by_input(model_type, empty_info_only):
|
|
|
|
| 281 |
return get_model_names_by_type_and_filter(model_type, {"empty_info_only":empty_info_only})
|
| 282 |
-
|
| 283 |
|
| 284 |
# get id from url
|
| 285 |
def get_model_id_from_url(url:str) -> str:
|
|
|
|
| 286 |
util.printD("Run get_model_id_from_url")
|
| 287 |
-
|
| 288 |
|
| 289 |
if not url:
|
| 290 |
util.printD("url or model id can not be empty")
|
| 291 |
return ""
|
| 292 |
|
| 293 |
if url.isnumeric():
|
| 294 |
-
# is already an
|
| 295 |
-
|
| 296 |
-
return
|
| 297 |
-
|
| 298 |
-
|
| 299 |
-
if len(
|
| 300 |
util.printD("url is not valid")
|
| 301 |
return ""
|
| 302 |
-
|
| 303 |
-
if
|
| 304 |
-
|
| 305 |
-
elif
|
| 306 |
-
|
| 307 |
else:
|
| 308 |
util.printD("There is no model id in this url")
|
| 309 |
return ""
|
| 310 |
-
|
| 311 |
-
return
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 312 |
|
| 313 |
|
| 314 |
# get preview image by model path
|
| 315 |
# image will be saved to file, so no return
|
| 316 |
-
def get_preview_image_by_model_path(model_path:str, max_size_preview,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 317 |
if not model_path:
|
| 318 |
util.printD("model_path is empty")
|
| 319 |
return
|
| 320 |
|
| 321 |
if not os.path.isfile(model_path):
|
| 322 |
-
util.printD("model_path is not a file:
|
| 323 |
return
|
| 324 |
|
| 325 |
-
base,
|
| 326 |
-
|
| 327 |
-
|
| 328 |
-
|
| 329 |
-
|
| 330 |
-
|
| 331 |
-
|
| 332 |
-
|
| 333 |
-
|
| 334 |
-
|
| 335 |
-
|
| 336 |
-
|
| 337 |
-
|
| 338 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 339 |
return
|
| 340 |
|
| 341 |
-
|
| 342 |
-
|
| 343 |
-
for img_dict in model_info["images"]:
|
| 344 |
-
if "nsfw" in img_dict.keys():
|
| 345 |
-
if img_dict["nsfw"] and img_dict["nsfw"] != "None":
|
| 346 |
-
util.printD("This image is NSFW")
|
| 347 |
-
if skip_nsfw_preview:
|
| 348 |
-
util.printD("Skip NSFW image")
|
| 349 |
-
continue
|
| 350 |
-
|
| 351 |
-
preview_type = img_dict.get("type")
|
| 352 |
-
if preview_type != "image":
|
| 353 |
-
util.printD(f"Unsupported preview type: {preview_type}, ignore.")
|
| 354 |
-
continue
|
| 355 |
-
|
| 356 |
-
if "url" in img_dict.keys():
|
| 357 |
-
img_url = img_dict["url"]
|
| 358 |
-
if max_size_preview:
|
| 359 |
-
# use max width
|
| 360 |
-
if "width" in img_dict.keys():
|
| 361 |
-
if img_dict["width"]:
|
| 362 |
-
img_url = get_full_size_image_url(img_url, img_dict["width"])
|
| 363 |
-
|
| 364 |
-
util.download_file(img_url, sec_preview)
|
| 365 |
-
# we only need 1 preview image
|
| 366 |
-
break
|
| 367 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 368 |
|
| 369 |
|
| 370 |
# search local model by version id in 1 folder, no subfolder
|
| 371 |
# return - model_info
|
| 372 |
def search_local_model_info_by_version_id(folder:str, version_id:int) -> dict:
|
|
|
|
|
|
|
|
|
|
| 373 |
util.printD("Searching local model by version id")
|
| 374 |
-
util.printD("folder:
|
| 375 |
-
util.printD("version_id:
|
| 376 |
|
| 377 |
if not folder:
|
| 378 |
util.printD("folder is none")
|
| 379 |
-
return
|
| 380 |
|
| 381 |
if not os.path.isdir(folder):
|
| 382 |
util.printD("folder is not a dir")
|
| 383 |
-
return
|
| 384 |
-
|
| 385 |
if not version_id:
|
| 386 |
util.printD("version_id is none")
|
| 387 |
-
return
|
| 388 |
-
|
| 389 |
# search civitai model info file
|
| 390 |
for filename in os.listdir(folder):
|
| 391 |
# check ext
|
| 392 |
base, ext = os.path.splitext(filename)
|
| 393 |
-
if ext == model.
|
| 394 |
# find info file
|
| 395 |
-
if len(base)
|
| 396 |
# not a civitai info file
|
| 397 |
continue
|
| 398 |
|
| 399 |
-
|
| 400 |
-
|
| 401 |
-
|
| 402 |
-
|
| 403 |
-
|
| 404 |
-
continue
|
| 405 |
|
| 406 |
-
|
| 407 |
-
|
|
|
|
| 408 |
|
| 409 |
-
|
| 410 |
-
|
| 411 |
-
|
|
|
|
| 412 |
|
| 413 |
-
|
| 414 |
-
if str(id) == str(version_id):
|
| 415 |
-
# find the one
|
| 416 |
-
return model_info
|
| 417 |
-
|
| 418 |
|
| 419 |
-
return
|
| 420 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 421 |
|
|
|
|
|
|
|
| 422 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 423 |
|
|
|
|
|
|
|
| 424 |
|
| 425 |
-
|
| 426 |
-
# return (model_path, model_id, model_name, new_verion_id, new_version_name, description, download_url, img_url)
|
| 427 |
-
def check_model_new_version_by_path(model_path:str, delay:float=1) -> tuple:
|
| 428 |
-
if not model_path:
|
| 429 |
-
util.printD("model_path is empty")
|
| 430 |
-
return
|
| 431 |
|
| 432 |
-
if not os.path.isfile(model_path):
|
| 433 |
-
util.printD("model_path is not a file: "+model_path)
|
| 434 |
-
return
|
| 435 |
-
|
| 436 |
-
# get model info file name
|
| 437 |
-
base, ext = os.path.splitext(model_path)
|
| 438 |
-
info_file = base + suffix + model.info_ext
|
| 439 |
-
|
| 440 |
-
if not os.path.isfile(info_file):
|
| 441 |
-
return
|
| 442 |
-
|
| 443 |
-
# get model info
|
| 444 |
-
model_info_file = model.load_model_info(info_file)
|
| 445 |
-
if not model_info_file:
|
| 446 |
-
return
|
| 447 |
|
| 448 |
-
|
| 449 |
-
|
| 450 |
-
|
| 451 |
-
|
| 452 |
-
|
| 453 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 454 |
|
| 455 |
-
if "modelId" not in model_info_file.keys():
|
| 456 |
-
return
|
| 457 |
-
|
| 458 |
-
model_id = model_info_file["modelId"]
|
| 459 |
-
if not model_id:
|
| 460 |
-
return
|
| 461 |
-
|
| 462 |
# get model info by id from civitai
|
| 463 |
model_info = get_model_info_by_id(model_id)
|
| 464 |
-
|
| 465 |
-
util.
|
| 466 |
-
time.sleep(delay)
|
| 467 |
|
| 468 |
if not model_info:
|
| 469 |
-
return
|
| 470 |
-
|
| 471 |
-
|
| 472 |
-
|
| 473 |
-
|
| 474 |
-
|
| 475 |
-
|
| 476 |
-
|
| 477 |
-
|
| 478 |
-
if not len(modelVersions):
|
| 479 |
-
return
|
| 480 |
-
|
| 481 |
-
current_version = modelVersions[0]
|
| 482 |
if not current_version:
|
| 483 |
-
return
|
| 484 |
-
|
| 485 |
-
if "id" not in current_version.keys():
|
| 486 |
-
return
|
| 487 |
-
|
| 488 |
-
current_version_id = current_version["id"]
|
| 489 |
-
if not current_version_id:
|
| 490 |
-
return
|
| 491 |
|
| 492 |
-
|
| 493 |
-
|
| 494 |
-
|
|
|
|
|
|
|
|
|
|
| 495 |
|
| 496 |
-
model_name = ""
|
| 497 |
-
|
| 498 |
-
|
| 499 |
-
|
| 500 |
-
if not model_name:
|
| 501 |
-
model_name = ""
|
| 502 |
-
|
| 503 |
-
|
| 504 |
-
new_version_name = ""
|
| 505 |
-
if "name" in current_version.keys():
|
| 506 |
-
new_version_name = current_version["name"]
|
| 507 |
-
|
| 508 |
-
if not new_version_name:
|
| 509 |
-
new_version_name = ""
|
| 510 |
-
|
| 511 |
-
description = ""
|
| 512 |
-
if "description" in current_version.keys():
|
| 513 |
-
description = current_version["description"]
|
| 514 |
-
|
| 515 |
-
if not description:
|
| 516 |
-
description = ""
|
| 517 |
-
|
| 518 |
-
downloadUrl = ""
|
| 519 |
-
if "downloadUrl" in current_version.keys():
|
| 520 |
-
downloadUrl = current_version["downloadUrl"]
|
| 521 |
-
|
| 522 |
-
if not downloadUrl:
|
| 523 |
-
downloadUrl = ""
|
| 524 |
|
| 525 |
# get 1 preview image
|
| 526 |
-
|
| 527 |
-
|
| 528 |
-
|
| 529 |
-
|
| 530 |
-
|
| 531 |
-
|
| 532 |
-
|
| 533 |
-
|
|
|
|
|
|
|
| 534 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 535 |
|
| 536 |
-
|
| 537 |
-
|
| 538 |
|
|
|
|
|
|
|
| 539 |
|
|
|
|
|
|
|
| 540 |
|
|
|
|
| 541 |
|
| 542 |
-
#
|
| 543 |
-
|
| 544 |
-
|
| 545 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 546 |
util.printD("Checking models' new version")
|
| 547 |
|
| 548 |
if not model_types:
|
| 549 |
return []
|
| 550 |
|
| 551 |
-
# check model types, which
|
| 552 |
mts = []
|
| 553 |
-
if
|
| 554 |
mts.append(model_types)
|
| 555 |
-
elif
|
| 556 |
mts = model_types
|
| 557 |
else:
|
| 558 |
-
util.printD("
|
| 559 |
util.printD(model_types)
|
| 560 |
return []
|
| 561 |
|
| 562 |
-
# output is a markdown document string to show a list of new versions on UI
|
| 563 |
-
output = ""
|
| 564 |
# new version list
|
| 565 |
new_versions = []
|
|
|
|
| 566 |
|
| 567 |
# walk all models
|
| 568 |
for model_type, model_folder in model.folders.items():
|
| 569 |
if model_type not in mts:
|
| 570 |
continue
|
| 571 |
|
| 572 |
-
util.printD("Scanning path:
|
| 573 |
-
for root,
|
| 574 |
for filename in files:
|
| 575 |
-
|
| 576 |
-
item = os.path.join(root, filename)
|
| 577 |
-
base, ext = os.path.splitext(item)
|
| 578 |
-
if ext in model.exts:
|
| 579 |
-
# find a model
|
| 580 |
-
r = check_model_new_version_by_path(item, delay)
|
| 581 |
-
|
| 582 |
-
if not r:
|
| 583 |
-
continue
|
| 584 |
-
|
| 585 |
-
model_path, model_id, model_name, current_version_id, new_version_name, description, downloadUrl, img_url = r
|
| 586 |
-
# check exist
|
| 587 |
-
if not current_version_id:
|
| 588 |
-
continue
|
| 589 |
-
|
| 590 |
-
# check this version id in list
|
| 591 |
-
is_already_in_list = False
|
| 592 |
-
for new_version in new_versions:
|
| 593 |
-
if current_version_id == new_version[3]:
|
| 594 |
-
# already in list
|
| 595 |
-
is_already_in_list = True
|
| 596 |
-
break
|
| 597 |
-
|
| 598 |
-
if is_already_in_list:
|
| 599 |
-
util.printD("New version is already in list")
|
| 600 |
-
continue
|
| 601 |
|
| 602 |
-
|
| 603 |
-
|
| 604 |
-
if target_model_info:
|
| 605 |
-
util.printD("New version is already existed")
|
| 606 |
-
continue
|
| 607 |
-
|
| 608 |
-
# add to list
|
| 609 |
-
new_versions.append(r)
|
| 610 |
|
|
|
|
|
|
|
| 611 |
|
|
|
|
|
|
|
| 612 |
|
|
|
|
|
|
|
|
|
|
| 613 |
|
| 614 |
return new_versions
|
| 615 |
-
|
| 616 |
-
|
| 617 |
-
|
|
|
|
| 1 |
+
""" -*- coding: UTF-8 -*-
|
| 2 |
+
handle msg between js and python side
|
| 3 |
+
"""
|
| 4 |
+
|
| 5 |
import os
|
|
|
|
|
|
|
| 6 |
import re
|
|
|
|
| 7 |
from . import util
|
| 8 |
from . import model
|
| 9 |
+
from . import downloader
|
| 10 |
|
| 11 |
+
SUFFIX = ".civitai"
|
| 12 |
|
| 13 |
+
URLS = {
|
| 14 |
"modelPage":"https://civitai.com/models/",
|
| 15 |
"modelId": "https://civitai.com/api/v1/models/",
|
| 16 |
"modelVersionId": "https://civitai.com/api/v1/model-versions/",
|
| 17 |
"hash": "https://civitai.com/api/v1/model-versions/by-hash/"
|
| 18 |
}
|
| 19 |
|
| 20 |
+
MODEL_TYPES = {
|
| 21 |
"Checkpoint": "ckp",
|
| 22 |
"TextualInversion": "ti",
|
| 23 |
"Hypernetwork": "hyper",
|
| 24 |
"LORA": "lora",
|
| 25 |
+
"LoCon": "lycoris",
|
| 26 |
}
|
| 27 |
|
| 28 |
+
FILE_TYPES = [
|
| 29 |
+
"Model", "Training Data", "Config", "VAE"
|
| 30 |
+
]
|
| 31 |
+
|
| 32 |
+
NSFW_LEVELS = ["None", "Soft", "Mature", "X", "Allow All"]
|
| 33 |
+
|
| 34 |
+
def civitai_get(civitai_url:str):
|
| 35 |
+
"""
|
| 36 |
+
Gets JSON from Civitai.
|
| 37 |
+
return: dict:json or None
|
| 38 |
+
"""
|
| 39 |
+
|
| 40 |
+
success, response = downloader.request_get(
|
| 41 |
+
civitai_url
|
| 42 |
+
)
|
| 43 |
+
|
| 44 |
+
if not success:
|
| 45 |
+
return None
|
| 46 |
+
|
| 47 |
+
# try to get content
|
| 48 |
+
content = None
|
| 49 |
+
try:
|
| 50 |
+
content = response.json()
|
| 51 |
+
except ValueError as e:
|
| 52 |
+
util.printD(util.indented_msg(
|
| 53 |
+
f"""
|
| 54 |
+
Parse response json failed
|
| 55 |
+
Error: {str(e)}
|
| 56 |
+
Response: {response.text}
|
| 57 |
+
"""
|
| 58 |
+
))
|
| 59 |
+
return None
|
| 60 |
+
|
| 61 |
+
return content
|
| 62 |
+
|
| 63 |
+
|
| 64 |
+
def append_parent_model_metadata(content):
|
| 65 |
+
"""
|
| 66 |
+
Some model metadata is stored in a "parent" context.
|
| 67 |
+
When we're fething a model by its hash, we're getting
|
| 68 |
+
the metadata for that model *file*, not the model entry
|
| 69 |
+
on Civitai, which may contain multiple versions.
|
| 70 |
|
| 71 |
+
This method gets the parent metadata and appends it to
|
| 72 |
+
our model file metadata.
|
| 73 |
|
| 74 |
+
return: model metadata with parent description, creator,
|
| 75 |
+
and permissions appended.
|
| 76 |
+
"""
|
| 77 |
+
util.printD("Fetching Parent Model Information")
|
| 78 |
+
parent_model = get_model_info_by_id(content["modelId"])
|
| 79 |
+
|
| 80 |
+
metadatas = [
|
| 81 |
+
"description", "tags", "allowNoCredit",
|
| 82 |
+
"allowCommercialUse", "allowDerivatives",
|
| 83 |
+
"allowDifferentLicense"
|
| 84 |
+
]
|
| 85 |
+
|
| 86 |
+
content["creator"] = parent_model.get("creator", "{}")
|
| 87 |
+
|
| 88 |
+
model_metadata = content["model"]
|
| 89 |
+
for metadata in metadatas:
|
| 90 |
+
model_metadata[metadata] = parent_model.get(metadata, "")
|
| 91 |
+
|
| 92 |
+
return content
|
| 93 |
|
| 94 |
|
| 95 |
# use this sha256 to get model info from civitai
|
| 96 |
# return: model info dict
|
| 97 |
+
def get_model_info_by_hash(model_hash:str):
|
| 98 |
+
"""
|
| 99 |
+
use this sha256 to get model info from civitai's api
|
| 100 |
+
|
| 101 |
+
return:
|
| 102 |
+
model info dict if a model is found
|
| 103 |
+
{} if civitai does not have the model
|
| 104 |
+
None if an error occurs.
|
| 105 |
+
"""
|
| 106 |
util.printD("Request model info from civitai")
|
| 107 |
|
| 108 |
+
if not model_hash:
|
| 109 |
util.printD("hash is empty")
|
| 110 |
+
return None
|
| 111 |
|
| 112 |
+
content = civitai_get(f'{URLS["hash"]}{model_hash}')
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 113 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 114 |
if not content:
|
| 115 |
+
return None
|
| 116 |
+
|
| 117 |
+
content = append_parent_model_metadata(content)
|
| 118 |
+
|
| 119 |
return content
|
| 120 |
|
| 121 |
|
| 122 |
+
def get_model_info_by_id(model_id:str) -> dict:
|
| 123 |
+
"""
|
| 124 |
+
Fetches model info by its model id.
|
| 125 |
+
returns: dict:model_info
|
| 126 |
+
"""
|
| 127 |
|
| 128 |
+
util.printD(f"Request model info from civitai: {model_id}")
|
|
|
|
| 129 |
|
| 130 |
+
if not model_id:
|
| 131 |
+
util.printD("model_id is empty")
|
| 132 |
+
return False
|
| 133 |
|
| 134 |
+
content = civitai_get(f'{URLS["modelId"]}{model_id}')
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 135 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 136 |
return content
|
| 137 |
|
| 138 |
|
| 139 |
+
def get_version_info_by_version_id(version_id:str) -> dict:
|
| 140 |
+
"""
|
| 141 |
+
Gets model version info from Civitai by version id
|
| 142 |
+
return: dict:model_info
|
| 143 |
+
"""
|
| 144 |
util.printD("Request version info from civitai")
|
| 145 |
|
| 146 |
+
if not version_id:
|
| 147 |
+
util.printD("version_id is empty")
|
| 148 |
+
return None
|
| 149 |
|
| 150 |
+
content = civitai_get(f'{URLS["modelVersionId"]}{version_id}')
|
| 151 |
+
|
| 152 |
+
if content:
|
| 153 |
+
content = append_parent_model_metadata(content)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 154 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 155 |
return content
|
| 156 |
|
| 157 |
|
| 158 |
+
def get_version_info_by_model_id(model_id:str) -> dict:
|
| 159 |
+
"""
|
| 160 |
+
Fetches version info by model id.
|
| 161 |
+
returns: dict:version_info
|
| 162 |
+
"""
|
| 163 |
|
| 164 |
+
model_info = get_model_info_by_id(model_id)
|
| 165 |
if not model_info:
|
| 166 |
+
util.printD(f"Failed to get model info by id: {model_id}")
|
| 167 |
+
return None
|
| 168 |
+
|
| 169 |
# check content to get version id
|
| 170 |
+
versions = model_info.get("modelVersions", [])
|
| 171 |
+
if len(versions) == 0:
|
| 172 |
+
util.printD("Found no model versions")
|
| 173 |
+
return None
|
| 174 |
+
|
| 175 |
+
def_version = versions[0]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 176 |
if not def_version:
|
| 177 |
util.printD("default version is None")
|
| 178 |
+
return None
|
| 179 |
+
|
| 180 |
+
version_id = def_version.get("id", "")
|
| 181 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
| 182 |
if not version_id:
|
| 183 |
+
util.printD("Could not get valid version id")
|
| 184 |
+
return None
|
| 185 |
|
| 186 |
# get version info
|
| 187 |
+
version_info = get_version_info_by_version_id(f"{version_id}")
|
| 188 |
if not version_info:
|
| 189 |
util.printD(f"Failed to get version info by version_id: {version_id}")
|
| 190 |
+
return None
|
| 191 |
|
| 192 |
return version_info
|
| 193 |
|
| 194 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 195 |
def load_model_info_by_search_term(model_type, search_term):
|
| 196 |
+
"""
|
| 197 |
+
get model info file's content by model type and search_term
|
| 198 |
+
parameter: model_type, search_term
|
| 199 |
+
return: model_info
|
| 200 |
+
"""
|
| 201 |
util.printD(f"Load model info of {search_term} in {model_type}")
|
| 202 |
+
if model.folders.get(model_type, None) is None:
|
| 203 |
+
util.printD(f"unknown model type: {model_type}")
|
| 204 |
+
return None
|
| 205 |
+
|
| 206 |
+
# search_term = f"{subfolderpath}{model name}{ext}"
|
| 207 |
+
# And it always start with a / even when there is no sub folder
|
| 208 |
+
base, _ = os.path.splitext(search_term)
|
| 209 |
model_info_base = base
|
| 210 |
if base[:1] == "/":
|
| 211 |
model_info_base = base[1:]
|
| 212 |
|
| 213 |
+
if model_type == "lora" and model.folders['lycoris']:
|
| 214 |
+
model_folders = [model.folders[model_type], model.folders['lycoris']]
|
| 215 |
+
else:
|
| 216 |
+
model_folders = [model.folders[model_type]]
|
| 217 |
|
| 218 |
+
for model_folder in model_folders:
|
| 219 |
+
model_info_filename = f"{model_info_base}{SUFFIX}{model.CIVITAI_EXT}"
|
| 220 |
+
model_info_filepath = os.path.join(model_folder, model_info_filename)
|
| 221 |
+
|
| 222 |
+
found = os.path.isfile(model_info_filepath)
|
| 223 |
+
|
| 224 |
+
if found:
|
| 225 |
+
break
|
| 226 |
|
| 227 |
+
if not found:
|
| 228 |
+
util.printD(f"Can not find model info file: {model_info_filepath}")
|
| 229 |
+
return None
|
| 230 |
|
| 231 |
+
return model.load_model_info(model_info_filepath)
|
| 232 |
|
| 233 |
|
| 234 |
+
def get_model_names_by_type_and_filter(model_type:str, metadata_filter:dict) -> list:
|
| 235 |
+
"""
|
| 236 |
+
get model file names by model type
|
| 237 |
+
parameter: model_type - string
|
| 238 |
+
parameter: filter - dict, which kind of model you need
|
| 239 |
+
return: model name list
|
| 240 |
+
"""
|
| 241 |
|
| 242 |
+
if model_type == "lora" and model.folders['lycoris']:
|
| 243 |
+
model_folders = [model.folders[model_type], model.folders['lycoris']]
|
| 244 |
+
else:
|
| 245 |
+
model_folders = [model.folders[model_type]]
|
|
|
|
|
|
|
|
|
|
| 246 |
|
| 247 |
+
# set metadata_filter
|
| 248 |
# only get models don't have a civitai info file
|
| 249 |
no_info_only = False
|
| 250 |
empty_info_only = False
|
| 251 |
|
| 252 |
+
if metadata_filter:
|
| 253 |
+
no_info_only = metadata_filter.get("no_info_only", False)
|
| 254 |
+
empty_info_only = metadata_filter.get("empty_info_only", False)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 255 |
|
| 256 |
# get information from filter
|
| 257 |
# only get those model names don't have a civitai model info file
|
| 258 |
model_names = []
|
| 259 |
+
for model_folder in model_folders:
|
| 260 |
+
for root, _, files in os.walk(model_folder, followlinks=True):
|
| 261 |
+
for filename in files:
|
| 262 |
+
if is_valid_file(root, filename, no_info_only, empty_info_only):
|
| 263 |
+
model_names.append(filename)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 264 |
|
| 265 |
return model_names
|
| 266 |
|
| 267 |
+
|
| 268 |
+
def is_valid_file(root, filename, no_info_only, empty_info_only):
|
| 269 |
+
"""
|
| 270 |
+
Filters through model files to determine if they are
|
| 271 |
+
valid targets for downloading new metadata.
|
| 272 |
+
|
| 273 |
+
return: bool
|
| 274 |
+
"""
|
| 275 |
+
item = os.path.join(root, filename)
|
| 276 |
+
# check extension
|
| 277 |
+
base, ext = os.path.splitext(item)
|
| 278 |
+
if ext not in model.EXTS:
|
| 279 |
+
return False
|
| 280 |
+
|
| 281 |
+
# find a model
|
| 282 |
+
info_file = f"{base}{SUFFIX}{model.CIVITAI_EXT}"
|
| 283 |
+
|
| 284 |
+
# check filter
|
| 285 |
+
if os.path.isfile(info_file):
|
| 286 |
+
if no_info_only:
|
| 287 |
+
return False
|
| 288 |
+
|
| 289 |
+
if empty_info_only:
|
| 290 |
+
# load model info
|
| 291 |
+
model_info = model.load_model_info(info_file)
|
| 292 |
+
# check content
|
| 293 |
+
if model_info and not model_info.get("id", "") == "":
|
| 294 |
+
# find a non-empty model info file
|
| 295 |
+
return False
|
| 296 |
+
|
| 297 |
+
return True
|
| 298 |
+
|
| 299 |
+
|
| 300 |
def get_model_names_by_input(model_type, empty_info_only):
|
| 301 |
+
""" return: list of model filenames with empty civitai info files """
|
| 302 |
return get_model_names_by_type_and_filter(model_type, {"empty_info_only":empty_info_only})
|
| 303 |
+
|
| 304 |
|
| 305 |
# get id from url
|
| 306 |
def get_model_id_from_url(url:str) -> str:
|
| 307 |
+
""" return: model_id from civitai url """
|
| 308 |
util.printD("Run get_model_id_from_url")
|
| 309 |
+
model_id = ""
|
| 310 |
|
| 311 |
if not url:
|
| 312 |
util.printD("url or model id can not be empty")
|
| 313 |
return ""
|
| 314 |
|
| 315 |
if url.isnumeric():
|
| 316 |
+
# is already an model_id
|
| 317 |
+
model_id = f"{url}"
|
| 318 |
+
return model_id
|
| 319 |
+
|
| 320 |
+
split_url = re.sub("\\?.+$", "", url).split("/")
|
| 321 |
+
if len(split_url) < 2:
|
| 322 |
util.printD("url is not valid")
|
| 323 |
return ""
|
| 324 |
+
|
| 325 |
+
if split_url[-2].isnumeric():
|
| 326 |
+
model_id = split_url[-2]
|
| 327 |
+
elif split_url[-1].isnumeric():
|
| 328 |
+
model_id = split_url[-1]
|
| 329 |
else:
|
| 330 |
util.printD("There is no model id in this url")
|
| 331 |
return ""
|
| 332 |
+
|
| 333 |
+
return model_id
|
| 334 |
+
|
| 335 |
+
|
| 336 |
+
def preview_exists(model_path):
|
| 337 |
+
""" Search for existing preview image. return True if it exists, else false """
|
| 338 |
+
|
| 339 |
+
previews = model.get_potential_model_preview_files(model_path)
|
| 340 |
+
|
| 341 |
+
for prev in previews:
|
| 342 |
+
if os.path.isfile(prev):
|
| 343 |
+
return True
|
| 344 |
+
|
| 345 |
+
return False
|
| 346 |
+
|
| 347 |
+
|
| 348 |
+
def should_skip(user_rating, image_rating):
|
| 349 |
+
""" return: True if preview_nsfw level higher than user threshold """
|
| 350 |
+
order = NSFW_LEVELS
|
| 351 |
+
if user_rating == "Skip":
|
| 352 |
+
# Old config
|
| 353 |
+
return False
|
| 354 |
+
if isinstance(image_rating, bool):
|
| 355 |
+
# Image using old NSFW system?
|
| 356 |
+
if image_rating:
|
| 357 |
+
image_rating = order[-1]
|
| 358 |
+
else:
|
| 359 |
+
image_rating = order[0]
|
| 360 |
+
return order.index(image_rating) >= order.index(user_rating)
|
| 361 |
+
|
| 362 |
+
|
| 363 |
+
def show_only_nsfw(user_rating, image_rating):
|
| 364 |
+
""" return: True if image is NSFW """
|
| 365 |
+
order = NSFW_LEVELS
|
| 366 |
+
if user_rating == "Skip":
|
| 367 |
+
# Old config
|
| 368 |
+
return True
|
| 369 |
+
return order.index(image_rating) <= order.index(user_rating)
|
| 370 |
+
|
| 371 |
+
|
| 372 |
+
def get_image_url(img_dict, max_size_preview):
|
| 373 |
+
"""
|
| 374 |
+
Create the image download URL
|
| 375 |
+
|
| 376 |
+
return: url:str
|
| 377 |
+
"""
|
| 378 |
+
|
| 379 |
+
url = img_dict["url"]
|
| 380 |
+
if max_size_preview:
|
| 381 |
+
# use max width
|
| 382 |
+
width = img_dict.get("width", False)
|
| 383 |
+
if width:
|
| 384 |
+
url = re.sub(r'/width=\d+/', '/width=' + str(width) + '/', url)
|
| 385 |
+
|
| 386 |
+
return url
|
| 387 |
+
|
| 388 |
+
|
| 389 |
+
def verify_preview(path, img_dict, max_size_preview, nsfw_preview_threshold):
|
| 390 |
+
"""
|
| 391 |
+
Downloads a preview image if it meets the user's requirements.
|
| 392 |
+
"""
|
| 393 |
+
|
| 394 |
+
img_url = img_dict.get("url", None)
|
| 395 |
+
if img_url is None:
|
| 396 |
+
yield (False, None)
|
| 397 |
+
|
| 398 |
+
image_rating = img_dict.get("nsfw", "None")
|
| 399 |
+
if image_rating != "None":
|
| 400 |
+
util.printD(f"This image is NSFW: {image_rating}")
|
| 401 |
+
if should_skip(nsfw_preview_threshold, image_rating):
|
| 402 |
+
util.printD("Skip NSFW image")
|
| 403 |
+
yield (False, None)
|
| 404 |
+
|
| 405 |
+
preview_type = img_dict.get("type")
|
| 406 |
+
if preview_type != "image":
|
| 407 |
+
util.printD(f"Preview is not an image. Found {preview_type} instead. Skipping.")
|
| 408 |
+
yield (False, None)
|
| 409 |
+
|
| 410 |
+
img_url = get_image_url(img_dict, max_size_preview)
|
| 411 |
+
|
| 412 |
+
success = False
|
| 413 |
+
preview_path = ""
|
| 414 |
+
for result in downloader.dl_file(img_url, file_path=path):
|
| 415 |
+
if not isinstance(result, str):
|
| 416 |
+
success, preview_path = result
|
| 417 |
+
break
|
| 418 |
+
|
| 419 |
+
yield result
|
| 420 |
+
|
| 421 |
+
|
| 422 |
+
if not success:
|
| 423 |
+
yield (False, None)
|
| 424 |
+
|
| 425 |
+
# we only need 1 preview image
|
| 426 |
+
yield (True, preview_path)
|
| 427 |
|
| 428 |
|
| 429 |
# get preview image by model path
|
| 430 |
# image will be saved to file, so no return
|
| 431 |
+
def get_preview_image_by_model_path(model_path:str, max_size_preview, nsfw_preview_threshold, preferred_preview=None):
|
| 432 |
+
"""
|
| 433 |
+
Downloads a preview image for a model if one doesn't already exist.
|
| 434 |
+
Skips images that are more NSFW than the user's NSFW threshold
|
| 435 |
+
"""
|
| 436 |
+
util.printD("Downloading model image.")
|
| 437 |
+
|
| 438 |
if not model_path:
|
| 439 |
util.printD("model_path is empty")
|
| 440 |
return
|
| 441 |
|
| 442 |
if not os.path.isfile(model_path):
|
| 443 |
+
util.printD(f"model_path is not a file: {model_path}")
|
| 444 |
return
|
| 445 |
|
| 446 |
+
base, _ = os.path.splitext(model_path)
|
| 447 |
+
preview_path = f"{base}.preview.png" # TODO png not strictly required
|
| 448 |
+
info_file = f"{base}{SUFFIX}{model.CIVITAI_EXT}"
|
| 449 |
+
|
| 450 |
+
# need to download preview image
|
| 451 |
+
util.printD(f"Checking preview image for model: {model_path}")
|
| 452 |
+
|
| 453 |
+
if preview_exists(model_path):
|
| 454 |
+
output = "Existing model image found. Skipping."
|
| 455 |
+
util.printD(output)
|
| 456 |
+
yield output
|
| 457 |
+
return
|
| 458 |
+
|
| 459 |
+
# load model_info file
|
| 460 |
+
if not os.path.isfile(info_file):
|
| 461 |
+
return
|
| 462 |
+
|
| 463 |
+
try:
|
| 464 |
+
images = model.load_model_info(info_file)["images"]
|
| 465 |
+
|
| 466 |
+
except (KeyError, TypeError):
|
| 467 |
+
return
|
| 468 |
+
|
| 469 |
+
if preferred_preview:
|
| 470 |
+
img_url = preferred_preview
|
| 471 |
+
for img_dict in images:
|
| 472 |
+
if img_dict["url"] == preferred_preview:
|
| 473 |
+
img_url = get_image_url(img_dict, max_size_preview)
|
| 474 |
+
break
|
| 475 |
+
|
| 476 |
+
for result in downloader.dl_file(img_url, file_path=preview_path):
|
| 477 |
+
if isinstance(result, str):
|
| 478 |
+
yield result
|
| 479 |
+
continue
|
| 480 |
+
|
| 481 |
+
success, msg = result
|
| 482 |
+
|
| 483 |
+
if success:
|
| 484 |
return
|
| 485 |
|
| 486 |
+
util.printD(msg)
|
| 487 |
+
util.printD("Failed to download preferred preview. Trying to find another")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 488 |
|
| 489 |
+
break
|
| 490 |
+
|
| 491 |
+
for img_dict in images:
|
| 492 |
+
for result in verify_preview(
|
| 493 |
+
preview_path, img_dict, max_size_preview, nsfw_preview_threshold
|
| 494 |
+
):
|
| 495 |
+
if not isinstance(result, str):
|
| 496 |
+
success, _ = result
|
| 497 |
+
# Only download one image
|
| 498 |
+
if success:
|
| 499 |
+
return
|
| 500 |
+
|
| 501 |
+
break
|
| 502 |
+
|
| 503 |
+
yield result
|
| 504 |
+
|
| 505 |
+
util.printD(f"Could not find any valid preview images for model: {model_path}")
|
| 506 |
+
yield
|
| 507 |
|
| 508 |
|
| 509 |
# search local model by version id in 1 folder, no subfolder
|
| 510 |
# return - model_info
|
| 511 |
def search_local_model_info_by_version_id(folder:str, version_id:int) -> dict:
|
| 512 |
+
""" Searches a folder for model_info files,
|
| 513 |
+
returns the model_info from a file if its id matches the model id.
|
| 514 |
+
"""
|
| 515 |
util.printD("Searching local model by version id")
|
| 516 |
+
util.printD(f"folder: {folder}")
|
| 517 |
+
util.printD(f"version_id: {version_id}")
|
| 518 |
|
| 519 |
if not folder:
|
| 520 |
util.printD("folder is none")
|
| 521 |
+
return None
|
| 522 |
|
| 523 |
if not os.path.isdir(folder):
|
| 524 |
util.printD("folder is not a dir")
|
| 525 |
+
return None
|
| 526 |
+
|
| 527 |
if not version_id:
|
| 528 |
util.printD("version_id is none")
|
| 529 |
+
return None
|
| 530 |
+
|
| 531 |
# search civitai model info file
|
| 532 |
for filename in os.listdir(folder):
|
| 533 |
# check ext
|
| 534 |
base, ext = os.path.splitext(filename)
|
| 535 |
+
if ext == model.CIVITAI_EXT:
|
| 536 |
# find info file
|
| 537 |
+
if not (len(base) > 8 and base[-8:] == SUFFIX):
|
| 538 |
# not a civitai info file
|
| 539 |
continue
|
| 540 |
|
| 541 |
+
# find a civitai info file
|
| 542 |
+
path = os.path.join(folder, filename)
|
| 543 |
+
model_info = model.load_model_info(path)
|
| 544 |
+
if not model_info:
|
| 545 |
+
continue
|
|
|
|
| 546 |
|
| 547 |
+
model_id = model_info.get("id", None)
|
| 548 |
+
if not model_id:
|
| 549 |
+
continue
|
| 550 |
|
| 551 |
+
# util.printD(f"Compare version id, src: {model_id}, target:{version_id}")
|
| 552 |
+
if f"{model_id}" == f"{version_id}":
|
| 553 |
+
# find the one
|
| 554 |
+
return model_info
|
| 555 |
|
| 556 |
+
return None
|
|
|
|
|
|
|
|
|
|
|
|
|
| 557 |
|
|
|
|
| 558 |
|
| 559 |
+
def get_model_id_from_model_path(model_path:str):
|
| 560 |
+
""" return model_id using model_path """
|
| 561 |
+
# get model info file name
|
| 562 |
+
base, _ = os.path.splitext(model_path)
|
| 563 |
+
info_file = f"{base}{SUFFIX}{model.CIVITAI_EXT}"
|
| 564 |
|
| 565 |
+
if not os.path.isfile(info_file):
|
| 566 |
+
return None
|
| 567 |
|
| 568 |
+
# get model info
|
| 569 |
+
model_info_file = model.load_model_info(info_file)
|
| 570 |
+
local_version_id = model_info_file.get("id", None)
|
| 571 |
+
model_id = model_info_file.get("modelId", None)
|
| 572 |
|
| 573 |
+
if None in [model_id, local_version_id]:
|
| 574 |
+
return None
|
| 575 |
|
| 576 |
+
return (model_id, local_version_id)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 577 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 578 |
|
| 579 |
+
def check_model_new_version_by_path(model_path:str, delay:float=0.2) -> tuple:
|
| 580 |
+
"""
|
| 581 |
+
check new version for a model by model path
|
| 582 |
+
return (
|
| 583 |
+
model_path, model_id, model_name, new_verion_id,
|
| 584 |
+
new_version_name, description, download_url, img_url
|
| 585 |
+
)
|
| 586 |
+
"""
|
| 587 |
+
|
| 588 |
+
if not (model_path and os.path.isfile(model_path)):
|
| 589 |
+
util.printD(f"model_path is not a file: {model_path}")
|
| 590 |
+
return None
|
| 591 |
+
|
| 592 |
+
result = get_model_id_from_model_path(model_path)
|
| 593 |
+
if not result:
|
| 594 |
+
return None
|
| 595 |
+
|
| 596 |
+
model_id, local_version_id = result
|
| 597 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 598 |
# get model info by id from civitai
|
| 599 |
model_info = get_model_info_by_id(model_id)
|
| 600 |
+
|
| 601 |
+
util.delay(delay)
|
|
|
|
| 602 |
|
| 603 |
if not model_info:
|
| 604 |
+
return None
|
| 605 |
+
|
| 606 |
+
model_versions = model_info.get("modelVersions", [])
|
| 607 |
+
|
| 608 |
+
if len(model_versions) == 0:
|
| 609 |
+
return None
|
| 610 |
+
|
| 611 |
+
current_version = model_versions[0]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 612 |
if not current_version:
|
| 613 |
+
return None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 614 |
|
| 615 |
+
current_version_id = current_version.get("id", False)
|
| 616 |
+
|
| 617 |
+
util.printD(f"Compare version id, local: {local_version_id}, remote: {current_version_id}")
|
| 618 |
+
|
| 619 |
+
if not (current_version_id and current_version_id != local_version_id):
|
| 620 |
+
return None
|
| 621 |
|
| 622 |
+
model_name = model_info.get("name", "")
|
| 623 |
+
new_version_name = current_version.get("name", "")
|
| 624 |
+
description = current_version.get("description", "")
|
| 625 |
+
download_url = current_version.get("downloadUrl", "")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 626 |
|
| 627 |
# get 1 preview image
|
| 628 |
+
try:
|
| 629 |
+
img_url = current_version["images"][0]["url"]
|
| 630 |
+
except (IndexError, KeyError):
|
| 631 |
+
img_url = ""
|
| 632 |
+
|
| 633 |
+
return (
|
| 634 |
+
model_path, model_id, model_name, current_version_id,
|
| 635 |
+
new_version_name, description, download_url, img_url
|
| 636 |
+
)
|
| 637 |
+
|
| 638 |
|
| 639 |
+
def check_single_model_new_version(root, filename, model_type, delay):
|
| 640 |
+
"""
|
| 641 |
+
return: True if a valid model has a new version.
|
| 642 |
+
"""
|
| 643 |
+
# check ext
|
| 644 |
+
item = os.path.join(root, filename)
|
| 645 |
+
_, ext = os.path.splitext(item)
|
| 646 |
|
| 647 |
+
if ext not in model.EXTS:
|
| 648 |
+
return False
|
| 649 |
|
| 650 |
+
# find a model
|
| 651 |
+
request = check_model_new_version_by_path(item, delay)
|
| 652 |
|
| 653 |
+
if not request:
|
| 654 |
+
return False
|
| 655 |
|
| 656 |
+
request = request + (model_type,)
|
| 657 |
|
| 658 |
+
# model_path, model_id, model_name, version_id, new_version_name, description, downloadUrl, img_url = request
|
| 659 |
+
version_id = request[3]
|
| 660 |
+
|
| 661 |
+
# check exist
|
| 662 |
+
if not version_id:
|
| 663 |
+
return False
|
| 664 |
+
|
| 665 |
+
# search this new version id to check if this model is already downloaded
|
| 666 |
+
target_model_info = search_local_model_info_by_version_id(root, version_id)
|
| 667 |
+
if target_model_info:
|
| 668 |
+
util.printD("New version is already exists")
|
| 669 |
+
return False
|
| 670 |
+
|
| 671 |
+
return request
|
| 672 |
+
|
| 673 |
+
|
| 674 |
+
def check_models_new_version_by_model_types(model_types:list, delay:float=0.2) -> list:
|
| 675 |
+
"""
|
| 676 |
+
check all models of model_types for new version
|
| 677 |
+
parameter: delay - float, how many seconds to delay between each request to civitai
|
| 678 |
+
return: new_versions
|
| 679 |
+
a list for all new versions, each one is
|
| 680 |
+
(model_path, model_id, model_name, new_verion_id,
|
| 681 |
+
new_version_name, description, download_url, img_url)
|
| 682 |
+
"""
|
| 683 |
util.printD("Checking models' new version")
|
| 684 |
|
| 685 |
if not model_types:
|
| 686 |
return []
|
| 687 |
|
| 688 |
+
# check model types, which could be a string as 1 type
|
| 689 |
mts = []
|
| 690 |
+
if isinstance(model_types, str):
|
| 691 |
mts.append(model_types)
|
| 692 |
+
elif isinstance(model_types, list):
|
| 693 |
mts = model_types
|
| 694 |
else:
|
| 695 |
+
util.printD("Unknown model types:")
|
| 696 |
util.printD(model_types)
|
| 697 |
return []
|
| 698 |
|
|
|
|
|
|
|
| 699 |
# new version list
|
| 700 |
new_versions = []
|
| 701 |
+
new_version_ids = []
|
| 702 |
|
| 703 |
# walk all models
|
| 704 |
for model_type, model_folder in model.folders.items():
|
| 705 |
if model_type not in mts:
|
| 706 |
continue
|
| 707 |
|
| 708 |
+
util.printD(f"Scanning path: {model_folder}")
|
| 709 |
+
for root, _, files in os.walk(model_folder, followlinks=True):
|
| 710 |
for filename in files:
|
| 711 |
+
version = check_single_model_new_version(root, filename, model_type, delay)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 712 |
|
| 713 |
+
if not version:
|
| 714 |
+
continue
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 715 |
|
| 716 |
+
# model_path, model_id, model_name, version_id, new_version_name, description, downloadUrl, img_url = version
|
| 717 |
+
version_id = version[3]
|
| 718 |
|
| 719 |
+
if version_id in new_version_ids:
|
| 720 |
+
continue
|
| 721 |
|
| 722 |
+
# add to list
|
| 723 |
+
new_versions.append(version)
|
| 724 |
+
new_version_ids.append(version_id)
|
| 725 |
|
| 726 |
return new_versions
|
|
|
|
|
|
|
|
|
extensions/Stable-Diffusion-Webui-Civitai-Helper/scripts/ch_lib/downloader.py
CHANGED
|
@@ -1,125 +1,368 @@
|
|
| 1 |
-
|
| 2 |
-
|
| 3 |
-
|
|
|
|
|
|
|
| 4 |
import os
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5 |
from . import util
|
| 6 |
|
| 7 |
|
| 8 |
-
|
|
|
|
| 9 |
|
| 10 |
# disable ssl warning info
|
| 11 |
-
|
| 12 |
|
| 13 |
-
# output is downloaded file path
|
| 14 |
-
def dl(url, folder, filename, filepath):
|
| 15 |
-
util.printD("Start downloading from: " + url)
|
| 16 |
-
# get file_path
|
| 17 |
-
file_path = ""
|
| 18 |
-
if filepath:
|
| 19 |
-
file_path = filepath
|
| 20 |
-
else:
|
| 21 |
-
# if file_path is not in parameter, then folder must be in parameter
|
| 22 |
-
if not folder:
|
| 23 |
-
util.printD("folder is none")
|
| 24 |
-
return
|
| 25 |
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 29 |
|
| 30 |
-
|
| 31 |
-
|
| 32 |
|
| 33 |
-
|
| 34 |
-
rh = requests.get(url, stream=True, verify=False, headers=util.def_headers, proxies=util.proxies)
|
| 35 |
-
# get file size
|
| 36 |
-
total_size = int(rh.headers.get('Content-Length', -1))
|
| 37 |
-
if (total_size < 0):
|
| 38 |
-
util.printD('This model requires an API key to download. More info: https://github.com/butaixianran/Stable-Diffusion-Webui-Civitai-Helper#civitai-api-key')
|
| 39 |
-
return
|
| 40 |
-
util.printD(f"File size: {total_size}")
|
| 41 |
-
|
| 42 |
-
# if file_path is empty, need to get file name from download url's header
|
| 43 |
-
if not file_path:
|
| 44 |
-
filename = ""
|
| 45 |
-
if "Content-Disposition" in rh.headers.keys():
|
| 46 |
-
# headers default is decoded with latin1, so need to re-decode it with utf-8
|
| 47 |
-
cd = rh.headers["Content-Disposition"].encode('latin1').decode('utf-8', errors='ignore')
|
| 48 |
-
# Extract the filename from the header
|
| 49 |
-
# content of a CD: "attachment;filename=FileName.txt"
|
| 50 |
-
# in case "" is in CD filename's start and end, need to strip them out
|
| 51 |
-
filename = cd.split("=")[1].strip('"')
|
| 52 |
-
if not filename:
|
| 53 |
-
util.printD("Fail to get file name from Content-Disposition: " + cd)
|
| 54 |
-
return
|
| 55 |
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 59 |
|
| 60 |
-
|
| 61 |
-
file_path = os.path.join(folder, filename)
|
| 62 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 63 |
|
| 64 |
-
|
| 65 |
-
base, ext = os.path.splitext(file_path)
|
| 66 |
|
| 67 |
-
|
| 68 |
-
count = 2
|
| 69 |
-
new_base = base
|
| 70 |
-
while os.path.isfile(file_path):
|
| 71 |
-
util.printD("Target file already exist.")
|
| 72 |
-
# re-name
|
| 73 |
-
new_base = base + "_" + str(count)
|
| 74 |
-
file_path = new_base + ext
|
| 75 |
-
count += 1
|
| 76 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 77 |
# use a temp file for downloading
|
| 78 |
-
dl_file_path = new_base+dl_ext
|
| 79 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 80 |
|
| 81 |
-
util.printD(f"Downloading to temp file: {
|
| 82 |
|
| 83 |
-
# check if downloading file
|
| 84 |
downloaded_size = 0
|
| 85 |
-
if os.path.exists(
|
| 86 |
-
downloaded_size = os.path.getsize(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 87 |
|
| 88 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 89 |
|
| 90 |
-
|
| 91 |
-
|
| 92 |
-
|
| 93 |
-
|
| 94 |
-
|
|
|
|
| 95 |
|
| 96 |
-
|
| 97 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 98 |
|
| 99 |
# write to file
|
| 100 |
-
with open(
|
| 101 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 102 |
if chunk:
|
|
|
|
| 103 |
downloaded_size += len(chunk)
|
| 104 |
-
|
| 105 |
-
|
| 106 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 107 |
|
| 108 |
-
|
| 109 |
-
|
| 110 |
-
|
| 111 |
-
|
| 112 |
-
|
|
|
|
|
|
|
| 113 |
|
| 114 |
-
|
| 115 |
|
| 116 |
# check file size
|
| 117 |
-
downloaded_size = os.path.getsize(
|
| 118 |
-
if downloaded_size
|
| 119 |
-
|
| 120 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 121 |
|
| 122 |
# rename file
|
| 123 |
-
os.rename(
|
| 124 |
-
|
| 125 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
""" -*- coding: UTF-8 -*-
|
| 2 |
+
Used for downloading files
|
| 3 |
+
"""
|
| 4 |
+
from __future__ import annotations
|
| 5 |
+
from collections.abc import Generator
|
| 6 |
import os
|
| 7 |
+
import platform
|
| 8 |
+
import time
|
| 9 |
+
from typing import cast, Literal
|
| 10 |
+
from tqdm import tqdm
|
| 11 |
+
import requests
|
| 12 |
+
import urllib3
|
| 13 |
from . import util
|
| 14 |
|
| 15 |
|
| 16 |
+
DL_EXT = ".downloading"
|
| 17 |
+
MAX_RETRIES = 3
|
| 18 |
|
| 19 |
# disable ssl warning info
|
| 20 |
+
urllib3.disable_warnings()
|
| 21 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 22 |
|
| 23 |
+
def request_get(
|
| 24 |
+
url:str,
|
| 25 |
+
headers:dict | None=None,
|
| 26 |
+
retries=0
|
| 27 |
+
) -> tuple[Literal[True], requests.Response] | tuple[Literal[False], str]:
|
| 28 |
+
"""
|
| 29 |
+
Performs a GET request
|
| 30 |
|
| 31 |
+
returns: tuple(success:bool, response:Response or failure message:str)
|
| 32 |
+
"""
|
| 33 |
|
| 34 |
+
headers = util.append_default_headers(headers or {})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 35 |
|
| 36 |
+
try:
|
| 37 |
+
response = requests.get(
|
| 38 |
+
url,
|
| 39 |
+
stream=True,
|
| 40 |
+
verify=False,
|
| 41 |
+
headers=headers,
|
| 42 |
+
proxies=util.PROXIES,
|
| 43 |
+
timeout=util.REQUEST_TIMEOUT
|
| 44 |
+
)
|
| 45 |
+
|
| 46 |
+
except TimeoutError:
|
| 47 |
+
output = f"GET Request timed out for {url}"
|
| 48 |
+
print(output)
|
| 49 |
+
return (False, output)
|
| 50 |
+
|
| 51 |
+
if not response.ok:
|
| 52 |
+
status_code = response.status_code
|
| 53 |
+
reason = response.reason
|
| 54 |
+
util.printD(util.indented_msg(
|
| 55 |
+
f"""
|
| 56 |
+
GET Request failed with error code:
|
| 57 |
+
{status_code}: {reason}
|
| 58 |
+
"""
|
| 59 |
+
))
|
| 60 |
+
|
| 61 |
+
if status_code == 401:
|
| 62 |
+
return (
|
| 63 |
+
False,
|
| 64 |
+
"This download requires Authentication. Please add an API Key to Civitai Helper's settings to continue this download. See [Wiki](https://github.com/zixaphir/Stable-Diffusion-Webui-Civitai-Helper/wiki/Civitai-API-Key) for details on how to create an API Key."
|
| 65 |
+
)
|
| 66 |
+
|
| 67 |
+
if status_code == 416:
|
| 68 |
+
response.raise_for_status()
|
| 69 |
+
|
| 70 |
+
if status_code != 404 and retries < MAX_RETRIES:
|
| 71 |
+
util.printD("Retrying")
|
| 72 |
+
return request_get(url, headers, retries + 1)
|
| 73 |
+
|
| 74 |
+
return (False, reason)
|
| 75 |
+
|
| 76 |
+
return (True, response)
|
| 77 |
+
|
| 78 |
+
|
| 79 |
+
def visualize_progress(percent:int, downloaded:int, total:int, speed:int | float, show_bar=True) -> str:
|
| 80 |
+
""" Used to display progress in webui """
|
| 81 |
+
|
| 82 |
+
s_total = f"{total}"
|
| 83 |
+
s_downloaded = f"{downloaded:>{len(s_total)}}"
|
| 84 |
+
s_percent = f"{percent:>3}"
|
| 85 |
+
s_speed = f'{human_readable_filesize(speed)}Bps'
|
| 86 |
|
| 87 |
+
snippet = f"`{s_percent}%: {s_downloaded} / {s_total} @ {s_speed}`"
|
|
|
|
| 88 |
|
| 89 |
+
if not show_bar:
|
| 90 |
+
# Unfortunately showing a progress bar in webui
|
| 91 |
+
# is very weird on mobile with limited horizontal
|
| 92 |
+
# space
|
| 93 |
+
return snippet.replace(" ", "\u00a0")
|
| 94 |
|
| 95 |
+
progress = "\u2588" * percent
|
|
|
|
| 96 |
|
| 97 |
+
return f"`[{progress:<100}] {snippet}`".replace(" ", "\u00a0")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 98 |
|
| 99 |
+
|
| 100 |
+
def download_progress(
|
| 101 |
+
url:str,
|
| 102 |
+
file_path:str,
|
| 103 |
+
total_size:int,
|
| 104 |
+
headers:dict | None=None,
|
| 105 |
+
response_without_range:requests.Response | None=None
|
| 106 |
+
) -> Generator[tuple[bool, str] | str, None, None]:
|
| 107 |
+
"""
|
| 108 |
+
Performs a file download.
|
| 109 |
+
|
| 110 |
+
yields: tuple(success:bool, filepath or failure message:str) or progress:str
|
| 111 |
+
"""
|
| 112 |
# use a temp file for downloading
|
|
|
|
| 113 |
|
| 114 |
+
if not headers:
|
| 115 |
+
headers = {}
|
| 116 |
+
|
| 117 |
+
dl_path = f"{file_path}{DL_EXT}"
|
| 118 |
|
| 119 |
+
util.printD(f"Downloading to temp file: {dl_path}")
|
| 120 |
|
| 121 |
+
# check if downloading file exists
|
| 122 |
downloaded_size = 0
|
| 123 |
+
if os.path.exists(dl_path):
|
| 124 |
+
downloaded_size = os.path.getsize(dl_path)
|
| 125 |
+
util.printD(f"Resuming partially downloaded file from progress: {downloaded_size}")
|
| 126 |
+
|
| 127 |
+
# use response without range or create request with range
|
| 128 |
+
if response_without_range and downloaded_size == 0:
|
| 129 |
+
response = response_without_range
|
| 130 |
+
else:
|
| 131 |
+
if response_without_range:
|
| 132 |
+
response_without_range.close()
|
| 133 |
|
| 134 |
+
# create header range
|
| 135 |
+
headers_with_range = util.append_default_headers({
|
| 136 |
+
**headers,
|
| 137 |
+
"Range": f"bytes={downloaded_size:d}-",
|
| 138 |
+
})
|
| 139 |
|
| 140 |
+
# download with header
|
| 141 |
+
try:
|
| 142 |
+
success, response_or_error = request_get(
|
| 143 |
+
url,
|
| 144 |
+
headers=headers_with_range,
|
| 145 |
+
)
|
| 146 |
|
| 147 |
+
except requests.HTTPError as dl_error:
|
| 148 |
+
# 416 - Range Not Satisfiable
|
| 149 |
+
response = dl_error.response
|
| 150 |
+
if not response or response.status_code != 416:
|
| 151 |
+
raise
|
| 152 |
+
|
| 153 |
+
util.printD("Could not resume download from existing temporary file. Restarting download")
|
| 154 |
+
|
| 155 |
+
os.remove(dl_path)
|
| 156 |
+
|
| 157 |
+
yield from download_progress(url, file_path, total_size, headers)
|
| 158 |
+
return
|
| 159 |
+
|
| 160 |
+
if not success:
|
| 161 |
+
yield (False, cast(str, response_or_error))
|
| 162 |
+
return
|
| 163 |
+
|
| 164 |
+
response = cast(requests.Response, response_or_error)
|
| 165 |
+
|
| 166 |
+
last_tick = 0
|
| 167 |
+
start = time.time()
|
| 168 |
+
|
| 169 |
+
downloaded_this_session = 0
|
| 170 |
|
| 171 |
# write to file
|
| 172 |
+
with open(dl_path, 'ab') as target, tqdm(
|
| 173 |
+
initial=target.tell(),
|
| 174 |
+
total=total_size,
|
| 175 |
+
unit='iB',
|
| 176 |
+
unit_scale=True,
|
| 177 |
+
unit_divisor=1024
|
| 178 |
+
) as progress_bar:
|
| 179 |
+
for chunk in response.iter_content(chunk_size=256*1024):
|
| 180 |
if chunk:
|
| 181 |
+
downloaded_this_session += len(chunk)
|
| 182 |
downloaded_size += len(chunk)
|
| 183 |
+
written = target.write(chunk)
|
| 184 |
+
|
| 185 |
+
# write to disk
|
| 186 |
+
target.flush()
|
| 187 |
+
|
| 188 |
+
progress_bar.update(written)
|
| 189 |
+
|
| 190 |
+
percent = int(100 * (downloaded_size / total_size))
|
| 191 |
+
timer = time.time()
|
| 192 |
+
|
| 193 |
+
# Gradio output is a *slooowwwwwwww* asynchronous FIFO queue
|
| 194 |
+
if timer - last_tick > 0.2 or percent == 100:
|
| 195 |
+
|
| 196 |
+
last_tick = timer
|
| 197 |
+
elapsed = timer - start
|
| 198 |
+
speed = downloaded_this_session // elapsed if elapsed >= 1 \
|
| 199 |
+
else downloaded_this_session
|
| 200 |
|
| 201 |
+
text_progress = visualize_progress(
|
| 202 |
+
percent,
|
| 203 |
+
downloaded_size,
|
| 204 |
+
total_size,
|
| 205 |
+
speed,
|
| 206 |
+
False
|
| 207 |
+
)
|
| 208 |
|
| 209 |
+
yield text_progress
|
| 210 |
|
| 211 |
# check file size
|
| 212 |
+
downloaded_size = os.path.getsize(dl_path)
|
| 213 |
+
if downloaded_size != total_size:
|
| 214 |
+
warning = util.indented_msg(
|
| 215 |
+
f"""
|
| 216 |
+
File is not the correct size: {file_path}.
|
| 217 |
+
Expected {total_size:d}, got {downloaded_size:d}.
|
| 218 |
+
The file may be corrupt. If you encounter issues,
|
| 219 |
+
you can try again later or download the file manually: {url}
|
| 220 |
+
"""
|
| 221 |
+
)
|
| 222 |
+
util.warning(warning)
|
| 223 |
+
util.printD(warning)
|
| 224 |
|
| 225 |
# rename file
|
| 226 |
+
os.rename(dl_path, file_path)
|
| 227 |
+
output = f"File Downloaded to: {file_path}"
|
| 228 |
+
util.printD(output)
|
| 229 |
+
|
| 230 |
+
yield (True, file_path)
|
| 231 |
+
|
| 232 |
+
|
| 233 |
+
def get_file_path_from_service_headers(response:requests.Response, folder:str) -> str | None:
|
| 234 |
+
"""
|
| 235 |
+
Parses a response header to get a filename
|
| 236 |
+
then builds a file_path.
|
| 237 |
+
|
| 238 |
+
return: file_path:str
|
| 239 |
+
"""
|
| 240 |
+
|
| 241 |
+
content_disposition = response.headers.get("Content-Disposition", None)
|
| 242 |
+
|
| 243 |
+
if content_disposition is None:
|
| 244 |
+
util.printD("Can not get file name from download url's header")
|
| 245 |
+
return None
|
| 246 |
+
|
| 247 |
+
# Extract the filename from the header
|
| 248 |
+
# content of a CD: "attachment;filename=FileName.txt"
|
| 249 |
+
# in case "" is in CD filename's start and end, need to strip them out
|
| 250 |
+
filename = content_disposition.split("=")[1].strip('"')
|
| 251 |
+
filename = filename.encode('iso8859-1').decode('utf-8')
|
| 252 |
+
if not filename:
|
| 253 |
+
util.printD(f"Fail to get file name from Content-Disposition: {content_disposition}")
|
| 254 |
+
return None
|
| 255 |
+
|
| 256 |
+
# with folder and filename, now we have the full file path
|
| 257 |
+
return os.path.join(folder, filename)
|
| 258 |
+
|
| 259 |
+
|
| 260 |
+
# output is downloaded file path
|
| 261 |
+
def dl_file(
|
| 262 |
+
url:str,
|
| 263 |
+
folder:str | None=None,
|
| 264 |
+
filename:str | None=None,
|
| 265 |
+
file_path:str | None=None,
|
| 266 |
+
headers:dict | None=None,
|
| 267 |
+
duplicate:str | None=None
|
| 268 |
+
) -> Generator[tuple[bool, str] | str, None, None]:
|
| 269 |
+
"""
|
| 270 |
+
Perform a download.
|
| 271 |
+
|
| 272 |
+
yields: tuple(success:bool, filepath or failure message:str) or progress:str
|
| 273 |
+
"""
|
| 274 |
+
|
| 275 |
+
if not headers:
|
| 276 |
+
headers = {}
|
| 277 |
+
|
| 278 |
+
success, response_or_error = request_get(url, headers=headers)
|
| 279 |
+
|
| 280 |
+
if not success:
|
| 281 |
+
yield (False, cast(str, response_or_error))
|
| 282 |
+
return
|
| 283 |
+
|
| 284 |
+
response = cast(requests.Response, response_or_error)
|
| 285 |
+
|
| 286 |
+
util.printD(f"Start downloading from: {url}")
|
| 287 |
+
|
| 288 |
+
# close the response when the function ends
|
| 289 |
+
with response:
|
| 290 |
+
|
| 291 |
+
# get file_path
|
| 292 |
+
if not file_path:
|
| 293 |
+
if not (folder and os.path.isdir(folder)):
|
| 294 |
+
yield (
|
| 295 |
+
False,
|
| 296 |
+
"No directory to save model to."
|
| 297 |
+
)
|
| 298 |
+
return
|
| 299 |
+
|
| 300 |
+
if filename:
|
| 301 |
+
file_path = os.path.join(folder, filename)
|
| 302 |
+
else:
|
| 303 |
+
file_path = get_file_path_from_service_headers(response, folder)
|
| 304 |
+
|
| 305 |
+
if not file_path:
|
| 306 |
+
yield (
|
| 307 |
+
False,
|
| 308 |
+
"Could not get a file_path to place saved file."
|
| 309 |
+
)
|
| 310 |
+
return
|
| 311 |
+
|
| 312 |
+
util.printD(f"Target file path: {file_path}")
|
| 313 |
+
base, ext = os.path.splitext(file_path)
|
| 314 |
+
|
| 315 |
+
# duplicate handling
|
| 316 |
+
if os.path.isfile(file_path):
|
| 317 |
+
if duplicate == "Rename New":
|
| 318 |
+
# check if file is already exist
|
| 319 |
+
count = 2
|
| 320 |
+
new_base = base
|
| 321 |
+
while os.path.isfile(file_path):
|
| 322 |
+
util.printD("Target file already exist.")
|
| 323 |
+
# rename duplicate
|
| 324 |
+
new_base = f"{base}_{count}"
|
| 325 |
+
file_path = f"{new_base}{ext}"
|
| 326 |
+
count += 1
|
| 327 |
+
|
| 328 |
+
elif duplicate != "Overwrite":
|
| 329 |
+
yield (
|
| 330 |
+
False,
|
| 331 |
+
f"File {file_path} already exists! Download will not proceed."
|
| 332 |
+
)
|
| 333 |
+
return
|
| 334 |
+
|
| 335 |
+
# get file size
|
| 336 |
+
total_size = int(response.headers['Content-Length'])
|
| 337 |
+
util.printD(f"File size: {total_size} ({human_readable_filesize(total_size)})")
|
| 338 |
+
|
| 339 |
+
yield from download_progress(url, file_path, total_size, headers, response)
|
| 340 |
+
|
| 341 |
+
|
| 342 |
+
def human_readable_filesize(size:int | float) -> str:
|
| 343 |
+
""" Convert file size to human readable text """
|
| 344 |
+
prefixes = ["", "K", "M", "G"]
|
| 345 |
+
|
| 346 |
+
# Mac reports filesizes in multiples of 1000
|
| 347 |
+
# We should respect platform differences
|
| 348 |
+
unit = 1000 if platform.system() == "Darwin" else 1024
|
| 349 |
+
|
| 350 |
+
i = 0
|
| 351 |
+
while size > unit and i < len(prefixes) - 1:
|
| 352 |
+
i += 1
|
| 353 |
+
size = size / unit
|
| 354 |
+
|
| 355 |
+
return f"{round(size, 2)}{prefixes[i]}"
|
| 356 |
+
|
| 357 |
+
|
| 358 |
+
def error(download_url:str, msg:str) -> str:
|
| 359 |
+
""" Display a download error """
|
| 360 |
+
output = util.indented_msg(
|
| 361 |
+
f"""
|
| 362 |
+
Download failed.
|
| 363 |
+
{msg}
|
| 364 |
+
Download url: {download_url}
|
| 365 |
+
"""
|
| 366 |
+
)
|
| 367 |
+
util.printD(output)
|
| 368 |
+
return output
|
extensions/Stable-Diffusion-Webui-Civitai-Helper/scripts/ch_lib/duplicate_check.py
ADDED
|
@@ -0,0 +1,392 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
""" -*- coding: UTF-8 -*-
|
| 2 |
+
Check for duplicate models.
|
| 3 |
+
"""
|
| 4 |
+
|
| 5 |
+
import os
|
| 6 |
+
import html
|
| 7 |
+
import json
|
| 8 |
+
import traceback
|
| 9 |
+
import gradio as gr
|
| 10 |
+
from . import util
|
| 11 |
+
from . import model
|
| 12 |
+
from . import civitai
|
| 13 |
+
from . import templates
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
def scan_for_dups(scan_model_types, cached_hash, progress=gr.Progress()):
|
| 17 |
+
""" Scans model metadata to detect duplicates
|
| 18 |
+
by using the model hash.
|
| 19 |
+
"""
|
| 20 |
+
|
| 21 |
+
util.printD("Start scan_for_dups")
|
| 22 |
+
output = ""
|
| 23 |
+
|
| 24 |
+
# check model types
|
| 25 |
+
if not scan_model_types:
|
| 26 |
+
output = "Model Types is None. Will not scan."
|
| 27 |
+
util.printD(output)
|
| 28 |
+
return output
|
| 29 |
+
|
| 30 |
+
model_types = []
|
| 31 |
+
|
| 32 |
+
# check type if it is a string
|
| 33 |
+
if isinstance(scan_model_types, str):
|
| 34 |
+
model_types.append(scan_model_types)
|
| 35 |
+
else:
|
| 36 |
+
model_types = scan_model_types
|
| 37 |
+
|
| 38 |
+
result = None
|
| 39 |
+
for result in gather_model_data(model_types, cached_hash):
|
| 40 |
+
if isinstance(result, tuple):
|
| 41 |
+
percent, status = result
|
| 42 |
+
progress(percent, desc=status)
|
| 43 |
+
models = result
|
| 44 |
+
|
| 45 |
+
dups = check_for_dups(models)
|
| 46 |
+
|
| 47 |
+
output = create_dups_html(dups)
|
| 48 |
+
|
| 49 |
+
return f"<section class=extra-network-cards>{output}</section>"
|
| 50 |
+
|
| 51 |
+
|
| 52 |
+
def gather_model_data(model_types, cached_hash):
|
| 53 |
+
""" Collects model metadata files and parses them for metadata """
|
| 54 |
+
|
| 55 |
+
models = {}
|
| 56 |
+
|
| 57 |
+
for model_type, model_folder in model.folders.items():
|
| 58 |
+
if model_type not in model_types:
|
| 59 |
+
continue
|
| 60 |
+
|
| 61 |
+
for result in scan_dir(model_folder, model_type, cached_hash):
|
| 62 |
+
yield result
|
| 63 |
+
|
| 64 |
+
models[model_type] = result
|
| 65 |
+
|
| 66 |
+
yield models
|
| 67 |
+
|
| 68 |
+
def scan_dir(model_folder, model_type, cached_hash):
|
| 69 |
+
"""
|
| 70 |
+
Scans dir for models and their metadata
|
| 71 |
+
"""
|
| 72 |
+
|
| 73 |
+
suffix = f"{civitai.SUFFIX}{model.CIVITAI_EXT}"
|
| 74 |
+
suffix_len = -len(suffix)
|
| 75 |
+
|
| 76 |
+
metadata = []
|
| 77 |
+
util.printD(f"Scanning path: {model_folder}")
|
| 78 |
+
for root, _, files in os.walk(model_folder, followlinks=True):
|
| 79 |
+
for filename in files:
|
| 80 |
+
try:
|
| 81 |
+
if filename[suffix_len:] == suffix:
|
| 82 |
+
for result in parse_metadata(model_folder, root, filename, suffix, model_type, cached_hash):
|
| 83 |
+
yield result
|
| 84 |
+
data = result
|
| 85 |
+
if data:
|
| 86 |
+
metadata.append(data)
|
| 87 |
+
|
| 88 |
+
except (IndexError, KeyError, ValueError):
|
| 89 |
+
util.printD(f"Error occurred on file `{root}/{filename}`")
|
| 90 |
+
traceback.print_exc()
|
| 91 |
+
util.printD("You can probably ignore this")
|
| 92 |
+
continue
|
| 93 |
+
|
| 94 |
+
yield metadata
|
| 95 |
+
|
| 96 |
+
|
| 97 |
+
def parse_metadata(model_folder, root, filename, suffix, model_type, cached_hash):
|
| 98 |
+
""" Parses model JSON file for hash / other metadata """
|
| 99 |
+
|
| 100 |
+
metadata = {}
|
| 101 |
+
|
| 102 |
+
filepath = f"{root}/{filename}"
|
| 103 |
+
model_name = filename[:-len(suffix)]
|
| 104 |
+
|
| 105 |
+
util.printD(f"Processing {model_name}")
|
| 106 |
+
|
| 107 |
+
with open(filepath) as file:
|
| 108 |
+
try:
|
| 109 |
+
model_info = json.load(file)
|
| 110 |
+
except json.JSONDecodeError:
|
| 111 |
+
yield None
|
| 112 |
+
return
|
| 113 |
+
|
| 114 |
+
model_file = model_info["files"][0]
|
| 115 |
+
model_ext = model_file["name"].split(".").pop()
|
| 116 |
+
|
| 117 |
+
description = None
|
| 118 |
+
|
| 119 |
+
# Backwards compatibility with older model info files
|
| 120 |
+
# from pre civitai helper 1.7
|
| 121 |
+
try:
|
| 122 |
+
description = model_info["model"]["description"]
|
| 123 |
+
except (ValueError, KeyError):
|
| 124 |
+
description = model_info.get("description", None)
|
| 125 |
+
|
| 126 |
+
if not description:
|
| 127 |
+
description = ""
|
| 128 |
+
|
| 129 |
+
model_path = f"{root}/{model_name}.{model_ext}"
|
| 130 |
+
|
| 131 |
+
if not os.path.isfile(model_path):
|
| 132 |
+
model_path = locate_model_from_partial(root, model_name)
|
| 133 |
+
|
| 134 |
+
if not (model_path and os.path.isfile(model_path)):
|
| 135 |
+
util.printD(f"No model path found for {filepath}")
|
| 136 |
+
yield None
|
| 137 |
+
return
|
| 138 |
+
|
| 139 |
+
for result in get_hash(model_path, model_file, model_type, cached_hash):
|
| 140 |
+
yield result
|
| 141 |
+
sha256 = result
|
| 142 |
+
|
| 143 |
+
metadata = {
|
| 144 |
+
"model_name": model_name,
|
| 145 |
+
"civitai_name": model_info["model"]["name"],
|
| 146 |
+
"description": description,
|
| 147 |
+
"model_path": model_path,
|
| 148 |
+
"subpath": model_path[len(model_folder):],
|
| 149 |
+
"model_type": model_type,
|
| 150 |
+
"hash": sha256,
|
| 151 |
+
"search_term": make_search_term(model_type, model_path, sha256)
|
| 152 |
+
}
|
| 153 |
+
|
| 154 |
+
yield metadata
|
| 155 |
+
|
| 156 |
+
|
| 157 |
+
def get_hash(model_path, model_file, model_type, cached_hash):
|
| 158 |
+
"""
|
| 159 |
+
Get or calculate hash of `model_path/model_file`
|
| 160 |
+
"""
|
| 161 |
+
|
| 162 |
+
sha256 = None
|
| 163 |
+
if cached_hash:
|
| 164 |
+
try:
|
| 165 |
+
sha256 = model_file["hashes"]["SHA256"].upper()
|
| 166 |
+
|
| 167 |
+
except (KeyError, ValueError):
|
| 168 |
+
pass
|
| 169 |
+
|
| 170 |
+
if sha256:
|
| 171 |
+
yield sha256
|
| 172 |
+
return
|
| 173 |
+
|
| 174 |
+
util.printD(f"No sha256 hash in metadata for {model_file}. \
|
| 175 |
+
\n\tGenerating one. This will be slower")
|
| 176 |
+
|
| 177 |
+
model_hash_type = {
|
| 178 |
+
"ckp": "checkpoint",
|
| 179 |
+
"ti": "textual_inversion",
|
| 180 |
+
"hyper": "hypernet",
|
| 181 |
+
"lora": "lora",
|
| 182 |
+
"lycoris": "lycoris"
|
| 183 |
+
}[model_type]
|
| 184 |
+
|
| 185 |
+
result = None
|
| 186 |
+
for result in util.gen_file_sha256(
|
| 187 |
+
model_path,
|
| 188 |
+
model_type=model_hash_type,
|
| 189 |
+
use_addnet_hash=False
|
| 190 |
+
):
|
| 191 |
+
yield result
|
| 192 |
+
|
| 193 |
+
sha256 = result.upper()
|
| 194 |
+
|
| 195 |
+
yield sha256
|
| 196 |
+
|
| 197 |
+
|
| 198 |
+
def make_search_term(model_type, model_path, sha256):
|
| 199 |
+
""" format search term for the correct model type """
|
| 200 |
+
|
| 201 |
+
folder = model.folders[model_type]
|
| 202 |
+
subpath = model_path[len(folder):]
|
| 203 |
+
sha256 = sha256.lower()
|
| 204 |
+
|
| 205 |
+
if not subpath.startswith("/"):
|
| 206 |
+
subpath = f"/{subpath}"
|
| 207 |
+
|
| 208 |
+
if model_type == "hyper":
|
| 209 |
+
snippet = subpath.split(".")
|
| 210 |
+
snippet.pop()
|
| 211 |
+
subpath = ".".join(snippet)
|
| 212 |
+
return f"{subpath}"
|
| 213 |
+
|
| 214 |
+
# All other supported model types seem to use this format
|
| 215 |
+
return f"{subpath} {sha256}"
|
| 216 |
+
|
| 217 |
+
|
| 218 |
+
def locate_model_from_partial(root, model_name):
|
| 219 |
+
"""
|
| 220 |
+
Tries to locate a model if the extension
|
| 221 |
+
doesn't match the metadata
|
| 222 |
+
"""
|
| 223 |
+
|
| 224 |
+
for ext in model.EXTS:
|
| 225 |
+
filename = f"{root}/{model_name}{ext}"
|
| 226 |
+
if os.path.isfile(filename):
|
| 227 |
+
return filename
|
| 228 |
+
|
| 229 |
+
return None
|
| 230 |
+
|
| 231 |
+
|
| 232 |
+
def check_for_dups(models):
|
| 233 |
+
"""
|
| 234 |
+
returns all models that share the a stored
|
| 235 |
+
sha256 hash with another model
|
| 236 |
+
"""
|
| 237 |
+
|
| 238 |
+
scanned = {}
|
| 239 |
+
dups = {}
|
| 240 |
+
|
| 241 |
+
for model_type, models_of_type in models.items():
|
| 242 |
+
scanned[model_type] = {}
|
| 243 |
+
scanned_type = scanned[model_type]
|
| 244 |
+
for model_data in models_of_type:
|
| 245 |
+
sha256 = model_data["hash"]
|
| 246 |
+
|
| 247 |
+
if model_type == "lycoris":
|
| 248 |
+
if is_lycoris_lora(model_data, scanned):
|
| 249 |
+
continue
|
| 250 |
+
|
| 251 |
+
if not scanned_type.get(sha256, None):
|
| 252 |
+
scanned_type[sha256] = [model_data]
|
| 253 |
+
continue
|
| 254 |
+
|
| 255 |
+
scanned_type[sha256].append(model_data)
|
| 256 |
+
|
| 257 |
+
for model_type, models_of_type in scanned.items():
|
| 258 |
+
dups_of_type = dups[model_type] = {}
|
| 259 |
+
for key, model_data in models_of_type.items():
|
| 260 |
+
if len(model_data) > 1:
|
| 261 |
+
dups_of_type[key] = model_data
|
| 262 |
+
|
| 263 |
+
return dups
|
| 264 |
+
|
| 265 |
+
|
| 266 |
+
def get_preview(model_path):
|
| 267 |
+
""" Finds the appropriate preview image for a model """
|
| 268 |
+
|
| 269 |
+
prevs = model.get_potential_model_preview_files(model_path, True)
|
| 270 |
+
|
| 271 |
+
bg_image = None
|
| 272 |
+
for prev in prevs:
|
| 273 |
+
if os.path.isfile(prev):
|
| 274 |
+
bg_image = prev
|
| 275 |
+
break
|
| 276 |
+
|
| 277 |
+
if not bg_image:
|
| 278 |
+
return ""
|
| 279 |
+
|
| 280 |
+
return templates.duplicate_preview.substitute(
|
| 281 |
+
bg_image=bg_image
|
| 282 |
+
)
|
| 283 |
+
|
| 284 |
+
|
| 285 |
+
def make_model_card(model_data):
|
| 286 |
+
""" Creates the HTML for a single model card """
|
| 287 |
+
|
| 288 |
+
card_t = templates.duplicate_card
|
| 289 |
+
|
| 290 |
+
bg_image = get_preview(model_data["model_path"])
|
| 291 |
+
style = "font-size:100%"
|
| 292 |
+
model_name = model_data["model_name"]
|
| 293 |
+
subpath = model_data["subpath"].replace("'", "\\'")
|
| 294 |
+
description = html.escape(model_data["description"])
|
| 295 |
+
search_term = model_data["search_term"].replace("'", "\\'")
|
| 296 |
+
model_type = model_data["model_type"]
|
| 297 |
+
|
| 298 |
+
util.printD(subpath)
|
| 299 |
+
|
| 300 |
+
card = card_t.substitute(
|
| 301 |
+
style=style,
|
| 302 |
+
name=model_name,
|
| 303 |
+
path=subpath,
|
| 304 |
+
background_image=bg_image,
|
| 305 |
+
description=description,
|
| 306 |
+
search_term=search_term,
|
| 307 |
+
model_type=model_type
|
| 308 |
+
)
|
| 309 |
+
|
| 310 |
+
return card
|
| 311 |
+
|
| 312 |
+
|
| 313 |
+
def create_dups_html(dups):
|
| 314 |
+
""" creates an HTML snippet containing duplicate models """
|
| 315 |
+
|
| 316 |
+
article_t = templates.duplicate_article
|
| 317 |
+
row_t = templates.duplicate_row
|
| 318 |
+
column_t = templates.duplicate_column
|
| 319 |
+
|
| 320 |
+
articles = []
|
| 321 |
+
|
| 322 |
+
for model_type, models_of_type in dups.items():
|
| 323 |
+
rows = []
|
| 324 |
+
for dup_data in models_of_type.values():
|
| 325 |
+
civitai_name = ""
|
| 326 |
+
sha256 = ""
|
| 327 |
+
|
| 328 |
+
columns = []
|
| 329 |
+
for count, model_data in enumerate(dup_data):
|
| 330 |
+
card = make_model_card(model_data)
|
| 331 |
+
|
| 332 |
+
column = column_t.substitute(
|
| 333 |
+
count=count,
|
| 334 |
+
card=card
|
| 335 |
+
)
|
| 336 |
+
|
| 337 |
+
columns.append(column)
|
| 338 |
+
|
| 339 |
+
if model_data["civitai_name"] and not civitai_name:
|
| 340 |
+
civitai_name = model_data["civitai_name"]
|
| 341 |
+
sha256 = model_data["hash"]
|
| 342 |
+
|
| 343 |
+
rows.append(
|
| 344 |
+
row_t.substitute(
|
| 345 |
+
civitai_name=civitai_name,
|
| 346 |
+
hash=sha256,
|
| 347 |
+
columns="".join(columns)
|
| 348 |
+
)
|
| 349 |
+
)
|
| 350 |
+
|
| 351 |
+
content = ""
|
| 352 |
+
if len(rows) > 0:
|
| 353 |
+
content = "".join(rows)
|
| 354 |
+
else:
|
| 355 |
+
content = f"No duplicate {model_type}s found!"
|
| 356 |
+
|
| 357 |
+
articles.append(
|
| 358 |
+
article_t.substitute(
|
| 359 |
+
section_name=model_type,
|
| 360 |
+
contents=content
|
| 361 |
+
)
|
| 362 |
+
)
|
| 363 |
+
|
| 364 |
+
if len(articles) < 1:
|
| 365 |
+
return "Found no duplicate models!"
|
| 366 |
+
|
| 367 |
+
return "".join(articles)
|
| 368 |
+
|
| 369 |
+
|
| 370 |
+
def is_lycoris_lora(lyco, models):
|
| 371 |
+
"""
|
| 372 |
+
Compares a lycoris model to scanned loras to ensure that they're not the same file.
|
| 373 |
+
"""
|
| 374 |
+
loras = None
|
| 375 |
+
try:
|
| 376 |
+
loras = models["lora"][lyco["hash"]]
|
| 377 |
+
|
| 378 |
+
except (KeyError, ValueError):
|
| 379 |
+
return False
|
| 380 |
+
|
| 381 |
+
try:
|
| 382 |
+
lyco_path = os.path.realpath(lyco["model_path"], strict=True)
|
| 383 |
+
|
| 384 |
+
for lora in loras:
|
| 385 |
+
lora_path = os.path.realpath(lora["model_path"], strict=True)
|
| 386 |
+
if lyco_path == lora_path:
|
| 387 |
+
return True
|
| 388 |
+
|
| 389 |
+
except OSError:
|
| 390 |
+
return False
|
| 391 |
+
|
| 392 |
+
return False
|
extensions/Stable-Diffusion-Webui-Civitai-Helper/scripts/ch_lib/js_action_civitai.py
CHANGED
|
@@ -1,8 +1,8 @@
|
|
| 1 |
-
|
| 2 |
-
|
|
|
|
| 3 |
import os
|
| 4 |
-
import
|
| 5 |
-
import requests
|
| 6 |
import webbrowser
|
| 7 |
from . import util
|
| 8 |
from . import model
|
|
@@ -11,19 +11,21 @@ from . import msg_handler
|
|
| 11 |
from . import downloader
|
| 12 |
|
| 13 |
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
|
|
|
|
|
|
| 19 |
util.printD("Start open_model_url")
|
| 20 |
|
| 21 |
output = ""
|
| 22 |
result = msg_handler.parse_js_msg(msg)
|
| 23 |
if not result:
|
| 24 |
util.printD("Parsing js ms failed")
|
| 25 |
-
return
|
| 26 |
-
|
| 27 |
model_type = result["model_type"]
|
| 28 |
search_term = result["search_term"]
|
| 29 |
|
|
@@ -41,16 +43,15 @@ def open_model_url(msg, open_url_with_js):
|
|
| 41 |
util.printD(f"model id from info file of {model_type} {search_term} is None")
|
| 42 |
return ""
|
| 43 |
|
| 44 |
-
url = civitai.
|
| 45 |
-
|
| 46 |
|
| 47 |
# msg content for js
|
| 48 |
content = {
|
| 49 |
-
"url":""
|
| 50 |
}
|
| 51 |
|
| 52 |
-
if not
|
| 53 |
-
util.printD("Open Url:
|
| 54 |
# open url
|
| 55 |
webbrowser.open_new_tab(url)
|
| 56 |
else:
|
|
@@ -62,50 +63,50 @@ def open_model_url(msg, open_url_with_js):
|
|
| 62 |
return output
|
| 63 |
|
| 64 |
|
| 65 |
-
|
| 66 |
-
# add trigger words to prompt
|
| 67 |
-
# parameter: model_type, search_term, prompt
|
| 68 |
-
# return: [new_prompt, new_prompt] - new prompt with trigger words, return twice for txt2img and img2img
|
| 69 |
def add_trigger_words(msg):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 70 |
util.printD("Start add_trigger_words")
|
| 71 |
|
| 72 |
result = msg_handler.parse_js_msg(msg)
|
| 73 |
if not result:
|
| 74 |
util.printD("Parsing js ms failed")
|
| 75 |
-
return
|
| 76 |
-
|
| 77 |
model_type = result["model_type"]
|
| 78 |
search_term = result["search_term"]
|
| 79 |
prompt = result["prompt"]
|
| 80 |
|
| 81 |
-
|
| 82 |
model_info = civitai.load_model_info_by_search_term(model_type, search_term)
|
| 83 |
if not model_info:
|
| 84 |
util.printD(f"Failed to get model info for {model_type} {search_term}")
|
| 85 |
return [prompt, prompt]
|
| 86 |
-
|
| 87 |
if "trainedWords" not in model_info.keys():
|
| 88 |
util.printD(f"Failed to get trainedWords from info file for {model_type} {search_term}")
|
| 89 |
return [prompt, prompt]
|
| 90 |
-
|
| 91 |
-
|
| 92 |
-
if
|
| 93 |
-
util.printD(f"No trainedWords from info file for {model_type} {search_term}")
|
| 94 |
-
return [prompt, prompt]
|
| 95 |
-
|
| 96 |
-
if len(trainedWords) == 0:
|
| 97 |
util.printD(f"trainedWords from info file for {model_type} {search_term} is empty")
|
| 98 |
return [prompt, prompt]
|
| 99 |
-
|
| 100 |
-
# get ful trigger words
|
| 101 |
-
trigger_words = ""
|
| 102 |
-
for word in trainedWords:
|
| 103 |
-
trigger_words = trigger_words + word + ", "
|
| 104 |
|
| 105 |
-
|
| 106 |
-
|
| 107 |
-
|
| 108 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 109 |
|
| 110 |
util.printD("End add_trigger_words")
|
| 111 |
|
|
@@ -113,18 +114,20 @@ def add_trigger_words(msg):
|
|
| 113 |
return [new_prompt, new_prompt]
|
| 114 |
|
| 115 |
|
| 116 |
-
|
| 117 |
-
# use preview image's prompt as prompt
|
| 118 |
-
# parameter: model_type, model_name, prompt, neg_prompt
|
| 119 |
-
# return: [new_prompt, new_neg_prompt, new_prompt, new_neg_prompt,] - return twice for txt2img and img2img
|
| 120 |
def use_preview_image_prompt(msg):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 121 |
util.printD("Start use_preview_image_prompt")
|
| 122 |
|
| 123 |
result = msg_handler.parse_js_msg(msg)
|
| 124 |
if not result:
|
| 125 |
util.printD("Parsing js ms failed")
|
| 126 |
-
return
|
| 127 |
-
|
| 128 |
model_type = result["model_type"]
|
| 129 |
search_term = result["search_term"]
|
| 130 |
prompt = result["prompt"]
|
|
@@ -135,188 +138,244 @@ def use_preview_image_prompt(msg):
|
|
| 135 |
if not model_info:
|
| 136 |
util.printD(f"Failed to get model info for {model_type} {search_term}")
|
| 137 |
return [prompt, neg_prompt, prompt, neg_prompt]
|
| 138 |
-
|
| 139 |
-
|
| 140 |
-
util.printD(f"Failed to get images from info file for {model_type} {search_term}")
|
| 141 |
-
return [prompt, neg_prompt, prompt, neg_prompt]
|
| 142 |
-
|
| 143 |
-
images = model_info["images"]
|
| 144 |
-
if not images:
|
| 145 |
-
util.printD(f"No images from info file for {model_type} {search_term}")
|
| 146 |
-
return [prompt, neg_prompt, prompt, neg_prompt]
|
| 147 |
-
|
| 148 |
if len(images) == 0:
|
| 149 |
-
util.printD(f"images from info file for {model_type} {search_term}
|
| 150 |
return [prompt, neg_prompt, prompt, neg_prompt]
|
| 151 |
-
|
| 152 |
# get prompt from preview images' meta data
|
| 153 |
preview_prompt = ""
|
| 154 |
preview_neg_prompt = ""
|
| 155 |
for img in images:
|
| 156 |
-
|
| 157 |
-
|
| 158 |
-
|
| 159 |
-
|
| 160 |
-
|
| 161 |
-
|
| 162 |
-
|
| 163 |
-
|
| 164 |
-
preview_neg_prompt = img["meta"]["negativePrompt"]
|
| 165 |
-
|
| 166 |
-
# we only need 1 prompt
|
| 167 |
-
if preview_prompt:
|
| 168 |
-
break
|
| 169 |
-
|
| 170 |
if not preview_prompt:
|
| 171 |
util.printD(f"There is no prompt of {model_type} {search_term} in its preview image")
|
| 172 |
return [prompt, neg_prompt, prompt, neg_prompt]
|
| 173 |
-
|
| 174 |
util.printD("End use_preview_image_prompt")
|
| 175 |
-
|
| 176 |
return [preview_prompt, preview_neg_prompt, preview_prompt, preview_neg_prompt]
|
| 177 |
|
| 178 |
|
| 179 |
-
|
| 180 |
-
|
| 181 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 182 |
util.printD("Start dl_model_new_version")
|
| 183 |
|
| 184 |
output = ""
|
| 185 |
|
|
|
|
|
|
|
| 186 |
result = msg_handler.parse_js_msg(msg)
|
| 187 |
if not result:
|
| 188 |
-
output = "Parsing js
|
| 189 |
util.printD(output)
|
| 190 |
-
|
| 191 |
-
|
|
|
|
| 192 |
model_path = result["model_path"]
|
| 193 |
version_id = result["version_id"]
|
| 194 |
download_url = result["download_url"]
|
| 195 |
-
|
| 196 |
-
util.printD("model_path: " + model_path)
|
| 197 |
-
util.printD("version_id: " + str(version_id))
|
| 198 |
-
util.printD("download_url: " + download_url)
|
| 199 |
|
| 200 |
# check data
|
| 201 |
-
if not model_path:
|
| 202 |
-
output = "
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 203 |
util.printD(output)
|
| 204 |
-
|
|
|
|
| 205 |
|
| 206 |
-
|
| 207 |
-
|
| 208 |
-
|
| 209 |
-
return output
|
| 210 |
-
|
| 211 |
-
if not download_url:
|
| 212 |
-
output = "download_url is empty"
|
| 213 |
-
util.printD(output)
|
| 214 |
-
return output
|
| 215 |
|
| 216 |
if not os.path.isfile(model_path):
|
| 217 |
-
output = "model_path is not a file:
|
| 218 |
util.printD(output)
|
| 219 |
-
|
|
|
|
| 220 |
|
| 221 |
# get model folder from model path
|
| 222 |
model_folder = os.path.dirname(model_path)
|
| 223 |
|
| 224 |
-
|
| 225 |
-
#
|
| 226 |
-
|
| 227 |
-
|
| 228 |
-
|
| 229 |
-
|
| 230 |
-
|
| 231 |
-
|
| 232 |
-
|
| 233 |
-
|
| 234 |
-
if not new_model_path:
|
| 235 |
-
output = "Download failed, check console log for detail. Download url: " + download_url
|
| 236 |
util.printD(output)
|
| 237 |
-
|
|
|
|
| 238 |
|
| 239 |
# get version info
|
| 240 |
version_info = civitai.get_version_info_by_version_id(version_id)
|
| 241 |
-
if not version_info:
|
| 242 |
-
output = "Model downloaded, but failed to get version info, check console log for detail. Model saved to: " + new_model_path
|
| 243 |
-
util.printD(output)
|
| 244 |
-
return output
|
| 245 |
|
| 246 |
-
# now write version info to
|
| 247 |
-
|
| 248 |
-
info_file = base + civitai.suffix + model.info_ext
|
| 249 |
-
model.write_model_info(info_file, version_info)
|
| 250 |
|
| 251 |
# then, get preview image
|
| 252 |
-
civitai.get_preview_image_by_model_path(
|
| 253 |
-
|
| 254 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 255 |
util.printD(output)
|
| 256 |
-
|
| 257 |
|
| 258 |
|
|
|
|
|
|
|
|
|
|
| 259 |
|
| 260 |
-
|
| 261 |
-
|
| 262 |
-
util.printD("Start remove_model_by_path")
|
| 263 |
-
|
| 264 |
-
output = ""
|
| 265 |
-
result = msg_handler.parse_js_msg(msg)
|
| 266 |
if not result:
|
| 267 |
output = "Parsing js ms failed"
|
|
|
|
| 268 |
util.printD(output)
|
| 269 |
-
return
|
| 270 |
-
|
| 271 |
model_type = result["model_type"]
|
| 272 |
search_term = result["search_term"]
|
| 273 |
|
| 274 |
model_path = model.get_model_path_by_search_term(model_type, search_term)
|
| 275 |
if not model_path:
|
| 276 |
output = f"Fail to get model for {model_type} {search_term}"
|
|
|
|
| 277 |
util.printD(output)
|
| 278 |
-
return
|
| 279 |
-
|
| 280 |
|
| 281 |
if not os.path.isfile(model_path):
|
| 282 |
output = f"Model {model_type} {search_term} does not exist, no need to remove"
|
|
|
|
| 283 |
util.printD(output)
|
| 284 |
-
return
|
| 285 |
-
|
| 286 |
-
|
| 287 |
-
|
| 288 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 289 |
|
|
|
|
|
|
|
| 290 |
|
| 291 |
-
|
| 292 |
-
base, ext = os.path.splitext(model_path)
|
| 293 |
-
info_path = base + model.info_ext
|
| 294 |
-
first_preview_path = base+".png"
|
| 295 |
-
sec_preview_path = base+".preview.png"
|
| 296 |
-
civitai_info_path = base + civitai.suffix + model.info_ext
|
| 297 |
|
| 298 |
-
|
| 299 |
-
related_paths.append(civitai_info_path)
|
| 300 |
|
| 301 |
-
if os.path.isfile(first_preview_path):
|
| 302 |
-
related_paths.append(first_preview_path)
|
| 303 |
|
| 304 |
-
|
| 305 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 306 |
|
| 307 |
-
|
| 308 |
-
|
| 309 |
|
| 310 |
-
|
| 311 |
-
for rp in related_paths:
|
| 312 |
-
if os.path.isfile(rp):
|
| 313 |
-
util.printD(f"Removing file {rp}")
|
| 314 |
-
os.remove(rp)
|
| 315 |
|
| 316 |
-
|
|
|
|
|
|
|
| 317 |
|
| 318 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 319 |
return output
|
| 320 |
|
| 321 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 322 |
|
|
|
|
|
|
|
|
|
| 1 |
+
""" -*- coding: UTF-8 -*-
|
| 2 |
+
handle msg between js and python side
|
| 3 |
+
"""
|
| 4 |
import os
|
| 5 |
+
from pathlib import Path
|
|
|
|
| 6 |
import webbrowser
|
| 7 |
from . import util
|
| 8 |
from . import model
|
|
|
|
| 11 |
from . import downloader
|
| 12 |
|
| 13 |
|
| 14 |
+
def open_model_url(msg):
|
| 15 |
+
"""
|
| 16 |
+
get civitai's model url and open it in browser
|
| 17 |
+
parameter: model_type, search_term
|
| 18 |
+
output: python msg
|
| 19 |
+
- will be sent to hidden textbox then picked by js side
|
| 20 |
+
"""
|
| 21 |
util.printD("Start open_model_url")
|
| 22 |
|
| 23 |
output = ""
|
| 24 |
result = msg_handler.parse_js_msg(msg)
|
| 25 |
if not result:
|
| 26 |
util.printD("Parsing js ms failed")
|
| 27 |
+
return None
|
| 28 |
+
|
| 29 |
model_type = result["model_type"]
|
| 30 |
search_term = result["search_term"]
|
| 31 |
|
|
|
|
| 43 |
util.printD(f"model id from info file of {model_type} {search_term} is None")
|
| 44 |
return ""
|
| 45 |
|
| 46 |
+
url = f'{civitai.URLS["modelPage"]}{model_id}'
|
|
|
|
| 47 |
|
| 48 |
# msg content for js
|
| 49 |
content = {
|
| 50 |
+
"url": ""
|
| 51 |
}
|
| 52 |
|
| 53 |
+
if not util.get_opts("ch_open_url_with_js"):
|
| 54 |
+
util.printD(f"Open Url: {url}")
|
| 55 |
# open url
|
| 56 |
webbrowser.open_new_tab(url)
|
| 57 |
else:
|
|
|
|
| 63 |
return output
|
| 64 |
|
| 65 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 66 |
def add_trigger_words(msg):
|
| 67 |
+
"""
|
| 68 |
+
add trigger words to prompt
|
| 69 |
+
parameter: model_type, search_term, prompt
|
| 70 |
+
return: [new_prompt, new_prompt]
|
| 71 |
+
- new prompt with trigger words, return twice for txt2img and img2img
|
| 72 |
+
"""
|
| 73 |
util.printD("Start add_trigger_words")
|
| 74 |
|
| 75 |
result = msg_handler.parse_js_msg(msg)
|
| 76 |
if not result:
|
| 77 |
util.printD("Parsing js ms failed")
|
| 78 |
+
return None
|
| 79 |
+
|
| 80 |
model_type = result["model_type"]
|
| 81 |
search_term = result["search_term"]
|
| 82 |
prompt = result["prompt"]
|
| 83 |
|
|
|
|
| 84 |
model_info = civitai.load_model_info_by_search_term(model_type, search_term)
|
| 85 |
if not model_info:
|
| 86 |
util.printD(f"Failed to get model info for {model_type} {search_term}")
|
| 87 |
return [prompt, prompt]
|
| 88 |
+
|
| 89 |
if "trainedWords" not in model_info.keys():
|
| 90 |
util.printD(f"Failed to get trainedWords from info file for {model_type} {search_term}")
|
| 91 |
return [prompt, prompt]
|
| 92 |
+
|
| 93 |
+
trained_words = model_info.get("trainedWords", [])
|
| 94 |
+
if len(trained_words) == 0:
|
|
|
|
|
|
|
|
|
|
|
|
|
| 95 |
util.printD(f"trainedWords from info file for {model_type} {search_term} is empty")
|
| 96 |
return [prompt, prompt]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 97 |
|
| 98 |
+
# guess if trained words are a list of words or list of prompts
|
| 99 |
+
prompt_list = ',' in trained_words[0]
|
| 100 |
+
|
| 101 |
+
# if a list of prompts, join with a newline, else a comma and a space
|
| 102 |
+
separator = "\n" if prompt_list else ", "
|
| 103 |
+
trigger_words = separator.join(trained_words)
|
| 104 |
+
|
| 105 |
+
new_prompt = f"{prompt} {trigger_words}"
|
| 106 |
+
|
| 107 |
+
util.printD(f"trigger_words: {trigger_words}")
|
| 108 |
+
util.printD(f"prompt: {prompt}")
|
| 109 |
+
util.printD(f"new_prompt: {new_prompt}")
|
| 110 |
|
| 111 |
util.printD("End add_trigger_words")
|
| 112 |
|
|
|
|
| 114 |
return [new_prompt, new_prompt]
|
| 115 |
|
| 116 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 117 |
def use_preview_image_prompt(msg):
|
| 118 |
+
"""
|
| 119 |
+
use preview image's prompt as prompt
|
| 120 |
+
parameter: model_type, model_name, prompt, neg_prompt
|
| 121 |
+
return: [new_prompt, new_neg_prompt, new_prompt, new_neg_prompt,]
|
| 122 |
+
- return twice for txt2img and img2img
|
| 123 |
+
"""
|
| 124 |
util.printD("Start use_preview_image_prompt")
|
| 125 |
|
| 126 |
result = msg_handler.parse_js_msg(msg)
|
| 127 |
if not result:
|
| 128 |
util.printD("Parsing js ms failed")
|
| 129 |
+
return None
|
| 130 |
+
|
| 131 |
model_type = result["model_type"]
|
| 132 |
search_term = result["search_term"]
|
| 133 |
prompt = result["prompt"]
|
|
|
|
| 138 |
if not model_info:
|
| 139 |
util.printD(f"Failed to get model info for {model_type} {search_term}")
|
| 140 |
return [prompt, neg_prompt, prompt, neg_prompt]
|
| 141 |
+
|
| 142 |
+
images = model_info.get("images", [])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 143 |
if len(images) == 0:
|
| 144 |
+
util.printD(f"No images from info file for {model_type} {search_term}")
|
| 145 |
return [prompt, neg_prompt, prompt, neg_prompt]
|
| 146 |
+
|
| 147 |
# get prompt from preview images' meta data
|
| 148 |
preview_prompt = ""
|
| 149 |
preview_neg_prompt = ""
|
| 150 |
for img in images:
|
| 151 |
+
meta = img.get("meta", {})
|
| 152 |
+
preview_prompt = meta.get("prompt", "")
|
| 153 |
+
preview_neg_prompt = meta.get("negativePrompt", "")
|
| 154 |
+
|
| 155 |
+
# we only need 1 prompt
|
| 156 |
+
if preview_prompt:
|
| 157 |
+
break
|
| 158 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 159 |
if not preview_prompt:
|
| 160 |
util.printD(f"There is no prompt of {model_type} {search_term} in its preview image")
|
| 161 |
return [prompt, neg_prompt, prompt, neg_prompt]
|
| 162 |
+
|
| 163 |
util.printD("End use_preview_image_prompt")
|
| 164 |
+
|
| 165 |
return [preview_prompt, preview_neg_prompt, preview_prompt, preview_neg_prompt]
|
| 166 |
|
| 167 |
|
| 168 |
+
def dl_model_new_version(msg, nsfw_preview_threshold):
|
| 169 |
+
"""
|
| 170 |
+
download model's new verson by model path, version id and download url
|
| 171 |
+
output is a md log
|
| 172 |
+
|
| 173 |
+
This method is triggered by a click event on the client/js
|
| 174 |
+
side that sends a signal to download a single new model
|
| 175 |
+
version. The actual check for new models is in
|
| 176 |
+
`model_action_civitai.check_models_new_version_to_md`.
|
| 177 |
+
return: output:str
|
| 178 |
+
"""
|
| 179 |
util.printD("Start dl_model_new_version")
|
| 180 |
|
| 181 |
output = ""
|
| 182 |
|
| 183 |
+
max_size_preview = util.get_opts("ch_max_size_preview")
|
| 184 |
+
|
| 185 |
result = msg_handler.parse_js_msg(msg)
|
| 186 |
if not result:
|
| 187 |
+
output = "Parsing js msg failed"
|
| 188 |
util.printD(output)
|
| 189 |
+
yield output
|
| 190 |
+
return
|
| 191 |
+
|
| 192 |
model_path = result["model_path"]
|
| 193 |
version_id = result["version_id"]
|
| 194 |
download_url = result["download_url"]
|
| 195 |
+
model_type = result["model_type"]
|
|
|
|
|
|
|
|
|
|
| 196 |
|
| 197 |
# check data
|
| 198 |
+
if not (model_path and version_id and download_url):
|
| 199 |
+
output = util.indented_msg(f"""
|
| 200 |
+
Missing parameter:
|
| 201 |
+
{model_path=}
|
| 202 |
+
{version_id=}
|
| 203 |
+
{download_url=}
|
| 204 |
+
""")
|
| 205 |
util.printD(output)
|
| 206 |
+
yield output
|
| 207 |
+
return
|
| 208 |
|
| 209 |
+
util.printD(f"model_path: {model_path}")
|
| 210 |
+
util.printD(f"version_id: {version_id}")
|
| 211 |
+
util.printD(f"download_url: {download_url}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 212 |
|
| 213 |
if not os.path.isfile(model_path):
|
| 214 |
+
output = f"model_path is not a file: {model_path}"
|
| 215 |
util.printD(output)
|
| 216 |
+
yield output
|
| 217 |
+
return
|
| 218 |
|
| 219 |
# get model folder from model path
|
| 220 |
model_folder = os.path.dirname(model_path)
|
| 221 |
|
| 222 |
+
success = False
|
| 223 |
+
# download file + webui visible progress bar
|
| 224 |
+
for result in downloader.dl_file(download_url, folder=model_folder):
|
| 225 |
+
if not isinstance(result, str):
|
| 226 |
+
success, output = result
|
| 227 |
+
break
|
| 228 |
+
|
| 229 |
+
yield result
|
| 230 |
+
|
| 231 |
+
if not success:
|
|
|
|
|
|
|
| 232 |
util.printD(output)
|
| 233 |
+
yield "Model download failed. See console for more details."
|
| 234 |
+
return
|
| 235 |
|
| 236 |
# get version info
|
| 237 |
version_info = civitai.get_version_info_by_version_id(version_id)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 238 |
|
| 239 |
+
# now write version info to files
|
| 240 |
+
model.process_model_info(output, version_info, model_type)
|
|
|
|
|
|
|
| 241 |
|
| 242 |
# then, get preview image
|
| 243 |
+
for result in civitai.get_preview_image_by_model_path(
|
| 244 |
+
output,
|
| 245 |
+
max_size_preview,
|
| 246 |
+
nsfw_preview_threshold
|
| 247 |
+
):
|
| 248 |
+
yield result
|
| 249 |
+
|
| 250 |
+
output = f"Done. Model downloaded to: {output}"
|
| 251 |
util.printD(output)
|
| 252 |
+
yield output
|
| 253 |
|
| 254 |
|
| 255 |
+
def get_model_path_from_js_msg(result):
|
| 256 |
+
"""
|
| 257 |
+
Gets a model path based on the webui js_msg.
|
| 258 |
|
| 259 |
+
return: model_path
|
| 260 |
+
"""
|
|
|
|
|
|
|
|
|
|
|
|
|
| 261 |
if not result:
|
| 262 |
output = "Parsing js ms failed"
|
| 263 |
+
util.error(output)
|
| 264 |
util.printD(output)
|
| 265 |
+
return None
|
| 266 |
+
|
| 267 |
model_type = result["model_type"]
|
| 268 |
search_term = result["search_term"]
|
| 269 |
|
| 270 |
model_path = model.get_model_path_by_search_term(model_type, search_term)
|
| 271 |
if not model_path:
|
| 272 |
output = f"Fail to get model for {model_type} {search_term}"
|
| 273 |
+
util.error(output)
|
| 274 |
util.printD(output)
|
| 275 |
+
return None
|
|
|
|
| 276 |
|
| 277 |
if not os.path.isfile(model_path):
|
| 278 |
output = f"Model {model_type} {search_term} does not exist, no need to remove"
|
| 279 |
+
util.error(output)
|
| 280 |
util.printD(output)
|
| 281 |
+
return None
|
| 282 |
+
|
| 283 |
+
return model_path
|
| 284 |
+
|
| 285 |
+
|
| 286 |
+
def make_new_filename(candidate_file, model_name, new_name):
|
| 287 |
+
"""
|
| 288 |
+
Substitutes and old model name for a new model name.
|
| 289 |
+
return: new_path:str or None
|
| 290 |
+
"""
|
| 291 |
+
path, filename = os.path.split(candidate_file)
|
| 292 |
+
|
| 293 |
+
if filename.index(model_name) != 0:
|
| 294 |
+
output = util.indented_msg(f"""
|
| 295 |
+
Could not find model_name in candidate file
|
| 296 |
+
{model_name=}
|
| 297 |
+
{candidate_file=}
|
| 298 |
+
{new_name=}
|
| 299 |
+
""")
|
| 300 |
+
util.error(output)
|
| 301 |
+
util.printD(output)
|
| 302 |
+
return None
|
| 303 |
|
| 304 |
+
# handles [model_name].civitai.info and [model_name].preview.[ext]
|
| 305 |
+
new_filename = filename.replace(model_name, new_name, 1)
|
| 306 |
|
| 307 |
+
new_path = os.path.join(path, new_filename)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 308 |
|
| 309 |
+
return new_path
|
|
|
|
| 310 |
|
|
|
|
|
|
|
| 311 |
|
| 312 |
+
def rename_model_by_path(msg):
|
| 313 |
+
"""
|
| 314 |
+
Rename a model file and all related ch_helper/
|
| 315 |
+
preview image files.
|
| 316 |
+
"""
|
| 317 |
+
util.printD("Start rename_model_by_path")
|
| 318 |
|
| 319 |
+
output = ""
|
| 320 |
+
result = msg_handler.parse_js_msg(msg)
|
| 321 |
|
| 322 |
+
model_path = get_model_path_from_js_msg(result)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 323 |
|
| 324 |
+
if model_path is None:
|
| 325 |
+
output = "Could not rename model."
|
| 326 |
+
return output
|
| 327 |
|
| 328 |
+
# all files need to be renamed
|
| 329 |
+
model_files = model.get_model_files_from_model_path(model_path)
|
| 330 |
+
model_name = Path(model_path).stem
|
| 331 |
+
new_name = util.bash_filename(result["new_name"])
|
| 332 |
+
|
| 333 |
+
renamed = []
|
| 334 |
+
for candidate_file in model_files:
|
| 335 |
+
new_path = make_new_filename(candidate_file, model_name, new_name)
|
| 336 |
+
if new_path is None:
|
| 337 |
+
continue
|
| 338 |
+
|
| 339 |
+
renamed.append(f"* {candidate_file} to {new_path}")
|
| 340 |
+
util.printD(f"Renaming file {candidate_file} to {new_path}")
|
| 341 |
+
os.rename(candidate_file, new_path)
|
| 342 |
+
|
| 343 |
+
renamed = "\n".join(renamed)
|
| 344 |
+
status = f"The following files were renamed: \n{renamed}"
|
| 345 |
+
util.info(status)
|
| 346 |
+
|
| 347 |
+
util.printD("End rename_model_by_path")
|
| 348 |
return output
|
| 349 |
|
| 350 |
|
| 351 |
+
def remove_model_by_path(msg):
|
| 352 |
+
"""
|
| 353 |
+
Remove a model file and all related ch_helper/
|
| 354 |
+
preview image files.
|
| 355 |
+
"""
|
| 356 |
+
output = ""
|
| 357 |
+
util.printD("Start remove_model_by_path")
|
| 358 |
+
|
| 359 |
+
result = msg_handler.parse_js_msg(msg)
|
| 360 |
+
|
| 361 |
+
model_path = get_model_path_from_js_msg(result)
|
| 362 |
+
|
| 363 |
+
if model_path is None:
|
| 364 |
+
output = "Could not remove model."
|
| 365 |
+
return output
|
| 366 |
+
|
| 367 |
+
# all files need to be renamed
|
| 368 |
+
model_files = model.get_model_files_from_model_path(model_path)
|
| 369 |
+
|
| 370 |
+
removed = []
|
| 371 |
+
for candidate_file in model_files:
|
| 372 |
+
util.printD(f"* Removing file {candidate_file}")
|
| 373 |
+
removed.append(candidate_file)
|
| 374 |
+
os.remove(candidate_file)
|
| 375 |
+
|
| 376 |
+
removed = "\n".join(removed)
|
| 377 |
+
status = f"The following files were removed: \n{removed}"
|
| 378 |
+
util.info(status)
|
| 379 |
|
| 380 |
+
util.printD("End remove_model_by_path")
|
| 381 |
+
return output
|
extensions/Stable-Diffusion-Webui-Civitai-Helper/scripts/ch_lib/model.py
CHANGED
|
@@ -1,35 +1,73 @@
|
|
| 1 |
-
|
| 2 |
-
|
|
|
|
| 3 |
import os
|
| 4 |
import json
|
| 5 |
-
|
|
|
|
|
|
|
|
|
|
| 6 |
from modules import shared
|
|
|
|
|
|
|
|
|
|
|
|
|
| 7 |
|
| 8 |
|
| 9 |
# this is the default root path
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 15 |
folders = {
|
| 16 |
-
"ti": os.path.join(
|
| 17 |
-
"hyper": os.path.join(
|
| 18 |
-
"ckp": os.path.join(
|
| 19 |
-
"lora": os.path.join(
|
|
|
|
| 20 |
}
|
| 21 |
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 25 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 26 |
|
| 27 |
-
|
|
|
|
| 28 |
def get_custom_model_folder():
|
|
|
|
|
|
|
|
|
|
| 29 |
util.printD("Get Custom Model Folder")
|
| 30 |
|
| 31 |
-
global folders
|
| 32 |
-
|
| 33 |
if shared.cmd_opts.embeddings_dir and os.path.isdir(shared.cmd_opts.embeddings_dir):
|
| 34 |
folders["ti"] = shared.cmd_opts.embeddings_dir
|
| 35 |
|
|
@@ -42,131 +80,602 @@ def get_custom_model_folder():
|
|
| 42 |
if shared.cmd_opts.lora_dir and os.path.isdir(shared.cmd_opts.lora_dir):
|
| 43 |
folders["lora"] = shared.cmd_opts.lora_dir
|
| 44 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 45 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 46 |
|
|
|
|
|
|
|
| 47 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 48 |
|
| 49 |
-
|
| 50 |
-
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 54 |
|
| 55 |
|
| 56 |
def load_model_info(path):
|
| 57 |
-
|
| 58 |
model_info = None
|
| 59 |
-
with open(os.path.realpath(path), 'r') as
|
| 60 |
try:
|
| 61 |
-
model_info = json.load(
|
| 62 |
-
except
|
| 63 |
-
util.printD("Selected file is not json:
|
| 64 |
-
|
| 65 |
-
|
| 66 |
-
|
| 67 |
return model_info
|
| 68 |
|
| 69 |
|
| 70 |
-
|
| 71 |
-
|
| 72 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 73 |
def get_model_names_by_type(model_type:str) -> list:
|
| 74 |
-
|
| 75 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 76 |
|
| 77 |
# get information from filter
|
| 78 |
# only get those model names don't have a civitai model info file
|
| 79 |
model_names = []
|
| 80 |
-
for
|
| 81 |
-
for
|
| 82 |
-
|
| 83 |
-
|
| 84 |
-
|
| 85 |
-
|
| 86 |
-
|
| 87 |
-
|
| 88 |
-
|
| 89 |
|
| 90 |
return model_names
|
| 91 |
|
| 92 |
|
| 93 |
# return 2 values: (model_root, model_path)
|
| 94 |
def get_model_path_by_type_and_name(model_type:str, model_name:str) -> str:
|
|
|
|
| 95 |
util.printD("Run get_model_path_by_type_and_name")
|
| 96 |
-
if model_type not in folders.keys():
|
| 97 |
-
util.printD("unknown model_type: " + model_type)
|
| 98 |
-
return
|
| 99 |
-
|
| 100 |
if not model_name:
|
| 101 |
util.printD("model name can not be empty")
|
| 102 |
-
return
|
| 103 |
-
|
| 104 |
-
folder = folders[model_type]
|
| 105 |
|
| 106 |
-
|
| 107 |
-
|
| 108 |
-
|
| 109 |
-
|
| 110 |
-
|
| 111 |
-
if filename == model_name:
|
| 112 |
-
# find model
|
| 113 |
-
model_root = root
|
| 114 |
-
model_path = os.path.join(root, filename)
|
| 115 |
-
return (model_root, model_path)
|
| 116 |
|
| 117 |
-
|
|
|
|
| 118 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 119 |
|
|
|
|
|
|
|
| 120 |
|
| 121 |
|
| 122 |
# get model path by model type and search_term
|
| 123 |
# parameter: model_type, search_term
|
| 124 |
# return: model_path
|
| 125 |
-
def get_model_path_by_search_term(model_type
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 126 |
util.printD(f"Search model of {search_term} in {model_type}")
|
| 127 |
-
if
|
| 128 |
util.printD("unknow model type: " + model_type)
|
| 129 |
-
return
|
| 130 |
-
|
| 131 |
-
# for lora: search_term = subfolderpath + model name + ext + " " + hash.
|
|
|
|
| 132 |
# for ckp: search_term = subfolderpath + model name + ext + " " + hash
|
| 133 |
# for ti: search_term = subfolderpath + model name + ext + " " + hash
|
| 134 |
# for hyper: search_term = subfolderpath + model name
|
| 135 |
-
has_hash = True
|
| 136 |
-
if model_type == "hyper":
|
| 137 |
-
has_hash = False
|
| 138 |
-
elif search_term.endswith(".pt") or search_term.endswith(".bin") or search_term.endswith(".safetensors") or search_term.endswith(".ckpt"):
|
| 139 |
-
has_hash = False
|
| 140 |
-
|
| 141 |
-
# remove hash
|
| 142 |
-
# model name may have multiple spaces
|
| 143 |
-
splited_path = search_term.split()
|
| 144 |
-
model_sub_path = splited_path[0]
|
| 145 |
-
if has_hash and len(splited_path) > 1:
|
| 146 |
-
model_sub_path = ""
|
| 147 |
-
for i in range(0, len(splited_path)-1):
|
| 148 |
-
model_sub_path += splited_path[i] + " "
|
| 149 |
-
|
| 150 |
-
model_sub_path = model_sub_path.strip()
|
| 151 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 152 |
|
| 153 |
if model_sub_path[:1] == "/":
|
| 154 |
model_sub_path = model_sub_path[1:]
|
| 155 |
|
| 156 |
-
if model_type == "
|
| 157 |
-
|
|
|
|
|
|
|
| 158 |
|
| 159 |
-
|
|
|
|
|
|
|
| 160 |
|
| 161 |
-
|
|
|
|
| 162 |
|
| 163 |
-
|
| 164 |
-
|
| 165 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 166 |
|
| 167 |
if not os.path.isfile(model_path):
|
| 168 |
-
util.printD("Can not find model file:
|
| 169 |
-
return
|
| 170 |
-
|
| 171 |
return model_path
|
| 172 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
""" -*- coding: UTF-8 -*-
|
| 2 |
+
Handle model operations
|
| 3 |
+
"""
|
| 4 |
import os
|
| 5 |
import json
|
| 6 |
+
import re
|
| 7 |
+
from PIL import Image
|
| 8 |
+
import piexif
|
| 9 |
+
import piexif.helper
|
| 10 |
from modules import shared
|
| 11 |
+
from modules import paths_internal
|
| 12 |
+
from . import civitai
|
| 13 |
+
from . import downloader
|
| 14 |
+
from . import util
|
| 15 |
|
| 16 |
|
| 17 |
# this is the default root path
|
| 18 |
+
ROOT_PATH = paths_internal.data_path
|
| 19 |
+
|
| 20 |
+
EXTS = (".bin", ".pt", ".safetensors", ".ckpt")
|
| 21 |
+
CIVITAI_EXT = ".info"
|
| 22 |
+
SDWEBUI_EXT = ".json"
|
| 23 |
+
|
| 24 |
+
"""
|
| 25 |
+
If command line arguement is used to change model folder,
|
| 26 |
+
then model folder is in absolute path, not based on this root path anymore.
|
| 27 |
+
so to make extension work with those absolute model folder paths, model
|
| 28 |
+
folder also need to be in absolute path
|
| 29 |
+
"""
|
| 30 |
folders = {
|
| 31 |
+
"ti": os.path.join(ROOT_PATH, "embeddings"),
|
| 32 |
+
"hyper": os.path.join(ROOT_PATH, "models", "hypernetworks"),
|
| 33 |
+
"ckp": os.path.join(ROOT_PATH, "models", "Stable-diffusion"),
|
| 34 |
+
"lora": os.path.join(ROOT_PATH, "models", "Lora"),
|
| 35 |
+
"lycoris": os.path.join(ROOT_PATH, "models", "LyCORIS"),
|
| 36 |
}
|
| 37 |
|
| 38 |
+
# Separate because the above is used for detecting supported models
|
| 39 |
+
# in other features
|
| 40 |
+
vae_folder = os.path.join(ROOT_PATH, "models", "VAE")
|
| 41 |
+
|
| 42 |
+
|
| 43 |
+
class VersionMismatchException(Exception):
|
| 44 |
+
""" Used for version comarison failures """
|
| 45 |
+
|
| 46 |
+
def __init__(self, value):
|
| 47 |
+
self.value = value
|
| 48 |
+
|
| 49 |
+
def __str__(self):
|
| 50 |
+
return repr(self.value)
|
| 51 |
+
|
| 52 |
|
| 53 |
+
def get_model_info_paths(model_path):
|
| 54 |
+
"""
|
| 55 |
+
Retrieve model info paths
|
| 56 |
+
return: (info_file:str, sd15_file:str)
|
| 57 |
+
"""
|
| 58 |
+
base, _ = os.path.splitext(model_path)
|
| 59 |
+
info_file = f"{base}{civitai.SUFFIX}{CIVITAI_EXT}"
|
| 60 |
+
sd15_file = f"{base}{SDWEBUI_EXT}"
|
| 61 |
+
return (info_file, sd15_file)
|
| 62 |
|
| 63 |
+
|
| 64 |
+
# get custom model path
|
| 65 |
def get_custom_model_folder():
|
| 66 |
+
"""
|
| 67 |
+
Update extra network directories with user-specified values.
|
| 68 |
+
"""
|
| 69 |
util.printD("Get Custom Model Folder")
|
| 70 |
|
|
|
|
|
|
|
| 71 |
if shared.cmd_opts.embeddings_dir and os.path.isdir(shared.cmd_opts.embeddings_dir):
|
| 72 |
folders["ti"] = shared.cmd_opts.embeddings_dir
|
| 73 |
|
|
|
|
| 80 |
if shared.cmd_opts.lora_dir and os.path.isdir(shared.cmd_opts.lora_dir):
|
| 81 |
folders["lora"] = shared.cmd_opts.lora_dir
|
| 82 |
|
| 83 |
+
if shared.cmd_opts.vae_dir and os.path.isdir(shared.cmd_opts.vae_dir):
|
| 84 |
+
vae_folder = shared.cmd_opts.vae_dir
|
| 85 |
+
|
| 86 |
+
if util.get_opts("ch_dl_lyco_to_lora"):
|
| 87 |
+
folders["lycoris"] = folders["lora"]
|
| 88 |
+
|
| 89 |
+
try:
|
| 90 |
+
# pre-1.5.0
|
| 91 |
+
if os.path.isdir(shared.cmd_opts.lyco_dir):
|
| 92 |
+
folders["lycoris"] = shared.cmd_opts.lyco_dir
|
| 93 |
+
|
| 94 |
+
except AttributeError:
|
| 95 |
+
try:
|
| 96 |
+
# sd-webui v1.5.1 added a backcompat option for lyco.
|
| 97 |
+
if os.path.isdir(shared.cmd_opts.lyco_dir_backcompat):
|
| 98 |
+
folders["lycoris"] = shared.cmd_opts.lyco_dir_backcompat
|
| 99 |
+
|
| 100 |
+
except AttributeError:
|
| 101 |
+
# v1.5.0 has no options for the Lyco dir:
|
| 102 |
+
# it is hardcoded as 'os.path.join(paths.models_path, "LyCORIS")'
|
| 103 |
+
return
|
| 104 |
+
|
| 105 |
+
|
| 106 |
+
def metadata_needed(info_file, sd15_file, refetch_old):
|
| 107 |
+
""" return True if metadata is needed
|
| 108 |
+
"""
|
| 109 |
+
|
| 110 |
+
need_civitai = metadata_needed_for_type(info_file, "civitai", refetch_old)
|
| 111 |
+
need_sdwebui = metadata_needed_for_type(sd15_file, "sdwebui", refetch_old)
|
| 112 |
+
|
| 113 |
+
return need_civitai or need_sdwebui
|
| 114 |
+
|
| 115 |
+
|
| 116 |
+
def metadata_needed_for_type(path, meta_type, refetch_old):
|
| 117 |
+
""" return True if metadata is needed for path
|
| 118 |
+
"""
|
| 119 |
+
|
| 120 |
+
if meta_type == "sdwebui" and not util.get_opts("ch_dl_webui_metadata"):
|
| 121 |
+
return False
|
| 122 |
+
|
| 123 |
+
if not os.path.isfile(path):
|
| 124 |
+
return True
|
| 125 |
+
|
| 126 |
+
if refetch_old:
|
| 127 |
+
metadata = None
|
| 128 |
+
with open(path) as file:
|
| 129 |
+
metadata = json.load(file)
|
| 130 |
+
|
| 131 |
+
metadata_version = util.metadata_version(metadata)
|
| 132 |
+
|
| 133 |
+
if not metadata_version:
|
| 134 |
+
return True
|
| 135 |
+
|
| 136 |
+
if meta_type == "civitai":
|
| 137 |
+
compat_version = util.COMPAT_VERSION_CIVITAI
|
| 138 |
+
else:
|
| 139 |
+
compat_version = util.COMPAT_VERSION_SDWEBUI
|
| 140 |
+
|
| 141 |
+
return util.newer_version(compat_version, metadata_version)
|
| 142 |
+
|
| 143 |
+
return False
|
| 144 |
+
|
| 145 |
|
| 146 |
+
def verify_overwrite_eligibility(path, new_data):
|
| 147 |
+
"""
|
| 148 |
+
Verifies a file is valid to be overwritten
|
| 149 |
+
Throws an error if the model ID does not match the new version's model ID
|
| 150 |
+
return: True if valid, False if not.
|
| 151 |
+
"""
|
| 152 |
+
if not os.path.isfile(path):
|
| 153 |
+
return True
|
| 154 |
|
| 155 |
+
with open(path, "r") as file:
|
| 156 |
+
old_data = json.load(file)
|
| 157 |
|
| 158 |
+
if "civitai" in path:
|
| 159 |
+
new_id = new_data.get("id", "")
|
| 160 |
+
old_id = old_data.get("id", "")
|
| 161 |
+
if new_id != old_id:
|
| 162 |
+
if old_id != "":
|
| 163 |
+
raise VersionMismatchException(
|
| 164 |
+
f"New metadata id ({new_id}) does not match old metadata id ({old_id})"
|
| 165 |
+
)
|
| 166 |
|
| 167 |
+
new_description = new_data.get("description", "")
|
| 168 |
+
old_description = old_data.get("description", "")
|
| 169 |
+
if new_description == "" and old_description != "":
|
| 170 |
+
util.printD(
|
| 171 |
+
f"New description is blank while old description contains data. Skipping {path}"
|
| 172 |
+
)
|
| 173 |
+
return False
|
| 174 |
+
|
| 175 |
+
return True
|
| 176 |
+
|
| 177 |
+
|
| 178 |
+
def write_info(data, path, info_type):
|
| 179 |
+
""" Writes model info to a file """
|
| 180 |
+
util.printD(f"Write model {info_type} info to file: {path}")
|
| 181 |
+
with open(os.path.realpath(path), 'w') as info_file:
|
| 182 |
+
info_file.write(json.dumps(data, indent=4))
|
| 183 |
+
|
| 184 |
+
|
| 185 |
+
def process_model_info(model_path, model_info, model_type="ckp", refetch_old=False):
|
| 186 |
+
"""
|
| 187 |
+
Write model info to file
|
| 188 |
+
|
| 189 |
+
SD1.5 Webui added saving model information to JSON files.
|
| 190 |
+
Much of this extension's metadata management is replicated
|
| 191 |
+
by this new functionality, including automatically adding
|
| 192 |
+
activator keywords to the prompt. It also provides a much
|
| 193 |
+
cleaner UI than civitai (not a high bar to clear) to
|
| 194 |
+
simply read a model's description.
|
| 195 |
+
|
| 196 |
+
So why not populate it with useful information?
|
| 197 |
+
|
| 198 |
+
Returns True if successful, otherwise an error message.
|
| 199 |
+
"""
|
| 200 |
+
|
| 201 |
+
if model_info is None:
|
| 202 |
+
util.printD("Failed to get model info.")
|
| 203 |
+
return
|
| 204 |
+
|
| 205 |
+
info_file, sd15_file = get_model_info_paths(model_path)
|
| 206 |
+
|
| 207 |
+
parent = model_info["model"]
|
| 208 |
+
|
| 209 |
+
description = parent.get("description", "")
|
| 210 |
+
if description:
|
| 211 |
+
description = util.trim_html(description)
|
| 212 |
+
parent["description"] = description
|
| 213 |
+
|
| 214 |
+
version_description = model_info.get("description", "")
|
| 215 |
+
if version_description:
|
| 216 |
+
version_description = util.trim_html(version_description)
|
| 217 |
+
model_info["description"] = version_description
|
| 218 |
+
|
| 219 |
+
# Create extension versioning information so that users
|
| 220 |
+
# can replace stale info files without newer entries.
|
| 221 |
+
model_info["extensions"] = util.create_extension_block(
|
| 222 |
+
model_info.get("extensions", None),
|
| 223 |
+
model_info.get("skeleton_file", False)
|
| 224 |
+
)
|
| 225 |
+
|
| 226 |
+
# civitai model info file
|
| 227 |
+
if metadata_needed_for_type(info_file, "civitai", refetch_old):
|
| 228 |
+
if refetch_old:
|
| 229 |
+
try:
|
| 230 |
+
if verify_overwrite_eligibility(info_file, model_info):
|
| 231 |
+
write_info(model_info, info_file, "civitai")
|
| 232 |
+
|
| 233 |
+
except VersionMismatchException as e:
|
| 234 |
+
util.printD(f"{e}, aborting")
|
| 235 |
+
return
|
| 236 |
+
|
| 237 |
+
else:
|
| 238 |
+
write_info(model_info, info_file, "civitai")
|
| 239 |
+
|
| 240 |
+
if not util.get_opts("ch_dl_webui_metadata"):
|
| 241 |
+
return
|
| 242 |
+
|
| 243 |
+
# Do not overwrite user-created files!
|
| 244 |
+
# TODO: maybe populate empty fields in existing files?
|
| 245 |
+
if not metadata_needed_for_type(sd15_file, "sdwebui", refetch_old):
|
| 246 |
+
util.printD(f"Metadata not needed for: {sd15_file}.")
|
| 247 |
+
return
|
| 248 |
+
|
| 249 |
+
process_sd15_info(sd15_file, model_info, parent, model_type, refetch_old)
|
| 250 |
+
|
| 251 |
+
|
| 252 |
+
def process_sd15_info(sd15_file, model_info, parent, model_type, refetch_old):
|
| 253 |
+
""" Creates/Processes [model_name].json """
|
| 254 |
+
|
| 255 |
+
# sd v1.5 model info file
|
| 256 |
+
sd_data = {}
|
| 257 |
+
|
| 258 |
+
sd_data["description"] = parent.get("description", "")
|
| 259 |
+
|
| 260 |
+
# I suppose notes are more for user notes, but populating it
|
| 261 |
+
# with potentially useful information about this particular
|
| 262 |
+
# version of the model is fine too, right? The user can
|
| 263 |
+
# always replace these if they're unneeded or add to them
|
| 264 |
+
version_info = model_info.get("description", None)
|
| 265 |
+
if version_info is not None:
|
| 266 |
+
sd_data["notes"] = version_info
|
| 267 |
+
|
| 268 |
+
# AFAIK civitai model versions are currently:
|
| 269 |
+
# SD 1.4, SD 1.5, SD 2.0, SD 2.0 786, SD 2.1, SD 2.1 786
|
| 270 |
+
# SD 2.1 Unclip, SDXL 0.9, SDXL 1.0, and Other.
|
| 271 |
+
# Conveniently, the 4th character is all we need for webui.
|
| 272 |
+
#
|
| 273 |
+
# INFO: On Civitai, all models list base model/"sd version".
|
| 274 |
+
# The SD WebUI interface only displays them for Lora/Lycoris.
|
| 275 |
+
#
|
| 276 |
+
# I'm populating the field anyways in hopes it eventually gets
|
| 277 |
+
# added.
|
| 278 |
+
base_model = model_info.get("baseModel", None)
|
| 279 |
+
sd_version = 'Unknown'
|
| 280 |
+
if base_model:
|
| 281 |
+
version = base_model[3]
|
| 282 |
+
|
| 283 |
+
sd_version = {
|
| 284 |
+
"1": 'SD1',
|
| 285 |
+
"2": 'SD2',
|
| 286 |
+
"L": 'SDXL',
|
| 287 |
+
}.get(version, 'Unknown')
|
| 288 |
+
|
| 289 |
+
sd_data["sd version"] = sd_version
|
| 290 |
+
|
| 291 |
+
for filedata in model_info["files"]:
|
| 292 |
+
if filedata["type"] == "VAE":
|
| 293 |
+
sd_data["vae"] = filedata["name"]
|
| 294 |
+
|
| 295 |
+
# INFO: On Civitai, all non-checkpoint models can have trained words.
|
| 296 |
+
# The SD WebUI interface only displays them for Lora/Lycoris.
|
| 297 |
+
# I'm populating the field anyways in hopes it eventually gets
|
| 298 |
+
# added.
|
| 299 |
+
#
|
| 300 |
+
# "trained words" usage is inconsistent among model authors.
|
| 301 |
+
# Some use each entry as an individual activator, while others
|
| 302 |
+
# use them as entire prompts
|
| 303 |
+
activator = model_info.get("trainedWords", [])
|
| 304 |
+
if (activator and activator[0]):
|
| 305 |
+
if "," in activator[0]:
|
| 306 |
+
# assume trainedWords is a prompt list
|
| 307 |
+
|
| 308 |
+
# webui does not support newlines in activator text
|
| 309 |
+
# so this is the best hinting I can give the user at the
|
| 310 |
+
# moment that these are mutually-exclusive prompts.
|
| 311 |
+
sd_data["activation text"] = " || ".join(activator)
|
| 312 |
+
else:
|
| 313 |
+
# assume trainedWords are single keywords
|
| 314 |
+
sd_data["activation text"] = ", ".join(activator)
|
| 315 |
+
|
| 316 |
+
# Sadly, Civitai does not provide default weight information,
|
| 317 |
+
# So 0 disables this functionality on webui's end and uses
|
| 318 |
+
# the user's global setting
|
| 319 |
+
if model_type in ["lora", "lycoris"]:
|
| 320 |
+
sd_data["preferred weight"] = 0
|
| 321 |
+
|
| 322 |
+
sd_data["extensions"] = util.create_extension_block(
|
| 323 |
+
model_info.get("extensions", None),
|
| 324 |
+
model_info.get("skeleton_file", False)
|
| 325 |
+
)
|
| 326 |
+
|
| 327 |
+
if refetch_old:
|
| 328 |
+
if verify_overwrite_eligibility(sd15_file, sd_data):
|
| 329 |
+
write_info(sd_data, sd15_file, "webui")
|
| 330 |
+
else:
|
| 331 |
+
write_info(sd_data, sd15_file, "webui")
|
| 332 |
|
| 333 |
|
| 334 |
def load_model_info(path):
|
| 335 |
+
""" Opens a JSON file and loads its JSON """
|
| 336 |
model_info = None
|
| 337 |
+
with open(os.path.realpath(path), 'r') as json_file:
|
| 338 |
try:
|
| 339 |
+
model_info = json.load(json_file)
|
| 340 |
+
except ValueError:
|
| 341 |
+
util.printD(f"Selected file is not json: {path}")
|
| 342 |
+
return None
|
| 343 |
+
|
|
|
|
| 344 |
return model_info
|
| 345 |
|
| 346 |
|
| 347 |
+
def get_potential_model_preview_files(model_path, all_prevs=False):
|
| 348 |
+
"""
|
| 349 |
+
Find existing preview images, if any.
|
| 350 |
+
|
| 351 |
+
Extensions from `find_preview` method in webui `modules/ui_extra_networks.py`
|
| 352 |
+
gif added in webui commit c602471b85d270e8c36707817d9bad92b0ff991e
|
| 353 |
+
|
| 354 |
+
return: preview_files
|
| 355 |
+
"""
|
| 356 |
+
preview_exts = ["png", "jpg", "jpeg", "webp", "gif"]
|
| 357 |
+
preview_files = []
|
| 358 |
+
|
| 359 |
+
base, _ = os.path.splitext(model_path)
|
| 360 |
+
|
| 361 |
+
for ext in preview_exts:
|
| 362 |
+
if all_prevs:
|
| 363 |
+
preview_files.append(f"{base}.{ext}")
|
| 364 |
+
preview_files.append(f"{base}.preview.{ext}")
|
| 365 |
+
|
| 366 |
+
return preview_files
|
| 367 |
+
|
| 368 |
+
|
| 369 |
+
def get_model_files_from_model_path(model_path):
|
| 370 |
+
""" return: list of paths """
|
| 371 |
+
|
| 372 |
+
base, _ = os.path.splitext(model_path)
|
| 373 |
+
|
| 374 |
+
info_file, sd15_file = get_model_info_paths(model_path)
|
| 375 |
+
user_preview_path = f"{base}.png"
|
| 376 |
+
|
| 377 |
+
paths = [model_path, info_file, sd15_file, user_preview_path]
|
| 378 |
+
preview_paths = get_potential_model_preview_files(model_path)
|
| 379 |
+
|
| 380 |
+
paths = paths + preview_paths
|
| 381 |
+
|
| 382 |
+
return [path for path in paths if os.path.isfile(path)]
|
| 383 |
+
|
| 384 |
+
|
| 385 |
def get_model_names_by_type(model_type:str) -> list:
|
| 386 |
+
"""
|
| 387 |
+
get model file names by model type
|
| 388 |
+
parameter: model_type - string
|
| 389 |
+
return: model name list
|
| 390 |
+
"""
|
| 391 |
+
|
| 392 |
+
if model_type == "lora" and folders['lycoris']:
|
| 393 |
+
model_folders = [folders[model_type], folders['lycoris']]
|
| 394 |
+
else:
|
| 395 |
+
model_folders = [folders[model_type]]
|
| 396 |
|
| 397 |
# get information from filter
|
| 398 |
# only get those model names don't have a civitai model info file
|
| 399 |
model_names = []
|
| 400 |
+
for model_folder in model_folders:
|
| 401 |
+
for root, _, files in os.walk(model_folder, followlinks=True):
|
| 402 |
+
for filename in files:
|
| 403 |
+
item = os.path.join(root, filename)
|
| 404 |
+
# check extension
|
| 405 |
+
_, ext = os.path.splitext(item)
|
| 406 |
+
if ext in EXTS:
|
| 407 |
+
# find a model
|
| 408 |
+
model_names.append(filename)
|
| 409 |
|
| 410 |
return model_names
|
| 411 |
|
| 412 |
|
| 413 |
# return 2 values: (model_root, model_path)
|
| 414 |
def get_model_path_by_type_and_name(model_type:str, model_name:str) -> str:
|
| 415 |
+
""" return: model_path:str matching model_name and model_type """
|
| 416 |
util.printD("Run get_model_path_by_type_and_name")
|
|
|
|
|
|
|
|
|
|
|
|
|
| 417 |
if not model_name:
|
| 418 |
util.printD("model name can not be empty")
|
| 419 |
+
return None
|
|
|
|
|
|
|
| 420 |
|
| 421 |
+
model_folders = [folders.get(model_type, None)]
|
| 422 |
+
|
| 423 |
+
if model_folders[0] is None:
|
| 424 |
+
util.printD(f"unknown model_type: {model_type}")
|
| 425 |
+
return None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 426 |
|
| 427 |
+
if model_type == "lora" and folders['lycoris']:
|
| 428 |
+
model_folders.append(folders['lycoris'])
|
| 429 |
|
| 430 |
+
# model could be in subfolder, need to walk.
|
| 431 |
+
model_path = util.find_file_in_folders(model_folders, model_name)
|
| 432 |
+
|
| 433 |
+
msg = util.indented_msg(f"""
|
| 434 |
+
Got following info:
|
| 435 |
+
{model_path=}
|
| 436 |
+
""")
|
| 437 |
+
util.printD(msg)
|
| 438 |
|
| 439 |
+
# May return `None`
|
| 440 |
+
return model_path
|
| 441 |
|
| 442 |
|
| 443 |
# get model path by model type and search_term
|
| 444 |
# parameter: model_type, search_term
|
| 445 |
# return: model_path
|
| 446 |
+
def get_model_path_by_search_term(model_type, search_term):
|
| 447 |
+
"""
|
| 448 |
+
Gets a model path based on the webui search term.
|
| 449 |
+
|
| 450 |
+
return: model_path
|
| 451 |
+
"""
|
| 452 |
util.printD(f"Search model of {search_term} in {model_type}")
|
| 453 |
+
if folders.get(model_type, None) is None:
|
| 454 |
util.printD("unknow model type: " + model_type)
|
| 455 |
+
return None
|
| 456 |
+
|
| 457 |
+
# for lora: search_term = subfolderpath + model name + ext + " " + hash.
|
| 458 |
+
# And it always start with a / even there is no sub folder
|
| 459 |
# for ckp: search_term = subfolderpath + model name + ext + " " + hash
|
| 460 |
# for ti: search_term = subfolderpath + model name + ext + " " + hash
|
| 461 |
# for hyper: search_term = subfolderpath + model name
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 462 |
|
| 463 |
+
# this used to be
|
| 464 |
+
# `model_sub_path = search_term.split()[0]`
|
| 465 |
+
# but it was failing on models containing spaces.
|
| 466 |
+
model_hash = search_term.split()[-1]
|
| 467 |
+
model_sub_path = search_term.replace(f" {model_hash}", "")
|
| 468 |
+
|
| 469 |
+
if model_type == "hyper":
|
| 470 |
+
model_sub_path = f"{search_term}.pt"
|
| 471 |
|
| 472 |
if model_sub_path[:1] == "/":
|
| 473 |
model_sub_path = model_sub_path[1:]
|
| 474 |
|
| 475 |
+
if model_type == "lora" and folders['lycoris']:
|
| 476 |
+
model_folders = [folders[model_type], folders['lycoris']]
|
| 477 |
+
else:
|
| 478 |
+
model_folders = [folders[model_type]]
|
| 479 |
|
| 480 |
+
for folder in model_folders:
|
| 481 |
+
model_folder = folder
|
| 482 |
+
model_path = os.path.join(model_folder, model_sub_path)
|
| 483 |
|
| 484 |
+
if os.path.isfile(model_path):
|
| 485 |
+
break
|
| 486 |
|
| 487 |
+
msg = util.indented_msg(f"""
|
| 488 |
+
Got following info:
|
| 489 |
+
{model_folder=}
|
| 490 |
+
{model_sub_path=}
|
| 491 |
+
{model_path=}
|
| 492 |
+
""")
|
| 493 |
+
util.printD(msg)
|
| 494 |
|
| 495 |
if not os.path.isfile(model_path):
|
| 496 |
+
util.printD(f"Can not find model file: {model_path}")
|
| 497 |
+
return None
|
| 498 |
+
|
| 499 |
return model_path
|
| 500 |
|
| 501 |
+
|
| 502 |
+
pattern = re.compile(r"\s*([^:,]+):\s*([^,]+)")
|
| 503 |
+
|
| 504 |
+
def sd_format(data):
|
| 505 |
+
"""
|
| 506 |
+
Parse image exif data for image creation parameters.
|
| 507 |
+
|
| 508 |
+
return parameters:dict or None
|
| 509 |
+
"""
|
| 510 |
+
|
| 511 |
+
if not data:
|
| 512 |
+
return None
|
| 513 |
+
|
| 514 |
+
prompt = ""
|
| 515 |
+
negative = ""
|
| 516 |
+
setting = ""
|
| 517 |
+
|
| 518 |
+
steps_index = data.find("\nSteps:")
|
| 519 |
+
|
| 520 |
+
if steps_index != -1:
|
| 521 |
+
prompt = data[:steps_index].strip()
|
| 522 |
+
setting = data[steps_index:].strip()
|
| 523 |
+
|
| 524 |
+
if "Negative prompt:" in data:
|
| 525 |
+
prompt_index = data.find("\nNegative prompt:")
|
| 526 |
+
|
| 527 |
+
if steps_index != -1:
|
| 528 |
+
negative = data[
|
| 529 |
+
prompt_index + len("Negative prompt:") + 1 : steps_index
|
| 530 |
+
].strip()
|
| 531 |
+
|
| 532 |
+
else:
|
| 533 |
+
negative = data[
|
| 534 |
+
prompt_index + len("Negative prompt:") + 1 :
|
| 535 |
+
].strip()
|
| 536 |
+
|
| 537 |
+
prompt = data[:prompt_index].strip()
|
| 538 |
+
|
| 539 |
+
elif steps_index == -1:
|
| 540 |
+
prompt = data
|
| 541 |
+
|
| 542 |
+
setting_dict = dict(re.findall(pattern, setting))
|
| 543 |
+
|
| 544 |
+
data = {
|
| 545 |
+
"prompt": prompt,
|
| 546 |
+
"negative": negative,
|
| 547 |
+
"Steps": setting_dict.get("Steps", ""),
|
| 548 |
+
"Sampler": setting_dict.get("Sampler", ""),
|
| 549 |
+
"CFG_scale": setting_dict.get("CFG scale", ""),
|
| 550 |
+
"Seed": setting_dict.get("Seed", ""),
|
| 551 |
+
"Size": setting_dict.get("Size", ""),
|
| 552 |
+
}
|
| 553 |
+
|
| 554 |
+
return data
|
| 555 |
+
|
| 556 |
+
|
| 557 |
+
def parse_image(image_file):
|
| 558 |
+
"""
|
| 559 |
+
Read image exif for userComment entry.
|
| 560 |
+
return: userComment:str
|
| 561 |
+
"""
|
| 562 |
+
data = None
|
| 563 |
+
with Image.open(image_file) as image:
|
| 564 |
+
if image.format == "PNG":
|
| 565 |
+
# However, unlike other image formats, EXIF data is not
|
| 566 |
+
# guaranteed to be present in info until load() has been called.
|
| 567 |
+
# https://pillow.readthedocs.io/en/stable/handbook/image-file-formats.html#png
|
| 568 |
+
image.load()
|
| 569 |
+
data = image.info.get("parameters")
|
| 570 |
+
|
| 571 |
+
elif image.format in ["JPEG", "WEBP"]:
|
| 572 |
+
try:
|
| 573 |
+
usercomment = piexif.ExifIFD.UserComment
|
| 574 |
+
exif = image.info.get("exif")
|
| 575 |
+
if not exif:
|
| 576 |
+
return None
|
| 577 |
+
jpegexif = piexif.load(exif) or {}
|
| 578 |
+
data = piexif.helper.UserComment.load(
|
| 579 |
+
jpegexif.get("Exif", {}).get(usercomment, None)
|
| 580 |
+
)
|
| 581 |
+
|
| 582 |
+
except (ValueError, TypeError):
|
| 583 |
+
util.printD("Failed to parse image exif.")
|
| 584 |
+
return None
|
| 585 |
+
|
| 586 |
+
return data
|
| 587 |
+
|
| 588 |
+
|
| 589 |
+
def get_remote_image_info(img_src):
|
| 590 |
+
"""
|
| 591 |
+
Download a remote image and parse out its creation parameters
|
| 592 |
+
|
| 593 |
+
return parameters:dict or None
|
| 594 |
+
"""
|
| 595 |
+
# anti-DDOS protection
|
| 596 |
+
util.delay(0.2)
|
| 597 |
+
|
| 598 |
+
success, response = downloader.request_get(img_src)
|
| 599 |
+
|
| 600 |
+
if not success:
|
| 601 |
+
return None
|
| 602 |
+
|
| 603 |
+
image_file = response.raw
|
| 604 |
+
try:
|
| 605 |
+
data = parse_image(image_file)
|
| 606 |
+
|
| 607 |
+
except OSError: #, UnidentifiedImageError
|
| 608 |
+
util.printD("Failed to open image.")
|
| 609 |
+
return None
|
| 610 |
+
|
| 611 |
+
if not data:
|
| 612 |
+
return None
|
| 613 |
+
|
| 614 |
+
sd_data = sd_format(data)
|
| 615 |
+
return sd_data
|
| 616 |
+
|
| 617 |
+
|
| 618 |
+
def update_civitai_info_image_meta(filename):
|
| 619 |
+
"""
|
| 620 |
+
Read model metadata and update missing image creation parameters,
|
| 621 |
+
if available.
|
| 622 |
+
"""
|
| 623 |
+
need_update = False
|
| 624 |
+
data = {}
|
| 625 |
+
|
| 626 |
+
if not os.path.isfile(filename):
|
| 627 |
+
return
|
| 628 |
+
|
| 629 |
+
with open(filename, 'r') as model_json:
|
| 630 |
+
data = json.load(model_json)
|
| 631 |
+
|
| 632 |
+
for image in data.get('images', []):
|
| 633 |
+
metadata = image.get('meta', None)
|
| 634 |
+
if not metadata and metadata != {}:
|
| 635 |
+
url = image.get("url", "")
|
| 636 |
+
if not url:
|
| 637 |
+
continue
|
| 638 |
+
|
| 639 |
+
util.printD(f"{filename} missing generation info for {url}. Processing {url}.")
|
| 640 |
+
|
| 641 |
+
image_data = get_remote_image_info(url)
|
| 642 |
+
if not image_data:
|
| 643 |
+
util.printD(f"Failed to find generation info on remote image at {url}.")
|
| 644 |
+
|
| 645 |
+
# "mark" image so additional runs will skip it.
|
| 646 |
+
image["meta"] = {}
|
| 647 |
+
need_update = True
|
| 648 |
+
continue
|
| 649 |
+
|
| 650 |
+
util.printD(
|
| 651 |
+
"The following information will be added to "
|
| 652 |
+
f"{filename} for {url}:\n{image_data}"
|
| 653 |
+
)
|
| 654 |
+
metadata = image_data
|
| 655 |
+
image["meta"] = metadata
|
| 656 |
+
|
| 657 |
+
need_update = True
|
| 658 |
+
|
| 659 |
+
if need_update:
|
| 660 |
+
with open(filename, 'w') as info_file:
|
| 661 |
+
json.dump(data, info_file, indent=4)
|
| 662 |
+
|
| 663 |
+
|
| 664 |
+
def scan_civitai_info_image_meta():
|
| 665 |
+
""" Search for *.civitai.info files """
|
| 666 |
+
util.printD("Start Scan_civitai_info_image_meta")
|
| 667 |
+
output = ""
|
| 668 |
+
count = 0
|
| 669 |
+
|
| 670 |
+
directories = [y for x, y in folders.items() if os.path.isdir(y)]
|
| 671 |
+
util.printD(f"{directories=}")
|
| 672 |
+
for directory in directories:
|
| 673 |
+
for root, _, files in os.walk(directory):
|
| 674 |
+
for filename in files:
|
| 675 |
+
if filename.endswith('.civitai.info'):
|
| 676 |
+
update_civitai_info_image_meta(os.path.join(root, filename))
|
| 677 |
+
count = count + 1
|
| 678 |
+
|
| 679 |
+
output = f"Done. Scanned {count} files."
|
| 680 |
+
util.printD(output)
|
| 681 |
+
return output
|
extensions/Stable-Diffusion-Webui-Civitai-Helper/scripts/ch_lib/model_action_civitai.py
CHANGED
|
@@ -1,511 +1,828 @@
|
|
| 1 |
-
|
| 2 |
-
|
|
|
|
| 3 |
import os
|
| 4 |
import time
|
|
|
|
|
|
|
|
|
|
| 5 |
from . import util
|
| 6 |
from . import model
|
| 7 |
from . import civitai
|
| 8 |
from . import downloader
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 9 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 10 |
|
| 11 |
-
# scan model to generate SHA256, then use this SHA256 to get model info from civitai
|
| 12 |
-
# return output msg
|
| 13 |
-
def scan_model(scan_model_types, max_size_preview, skip_nsfw_preview):
|
| 14 |
util.printD("Start scan_model")
|
| 15 |
output = ""
|
| 16 |
|
|
|
|
|
|
|
| 17 |
# check model types
|
| 18 |
if not scan_model_types:
|
| 19 |
output = "Model Types is None, can not scan."
|
| 20 |
util.printD(output)
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
if
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
model_count = 0
|
| 31 |
-
image_count = 0
|
| 32 |
-
# scan_log = ""
|
| 33 |
for model_type, model_folder in model.folders.items():
|
| 34 |
if model_type not in model_types:
|
| 35 |
continue
|
| 36 |
|
| 37 |
-
util.printD("Scanning path:
|
| 38 |
-
for root,
|
| 39 |
for filename in files:
|
|
|
|
| 40 |
# check ext
|
| 41 |
-
|
| 42 |
-
|
| 43 |
-
if ext in model.
|
| 44 |
-
|
| 45 |
-
if len(base) > 4:
|
| 46 |
-
if base[-4:] == model.vae_suffix:
|
| 47 |
-
# find .vae
|
| 48 |
-
util.printD("This is a vae file: " + filename)
|
| 49 |
-
continue
|
| 50 |
-
|
| 51 |
-
# find a model
|
| 52 |
-
# get info file
|
| 53 |
-
info_file = base + civitai.suffix + model.info_ext
|
| 54 |
-
# check info file
|
| 55 |
-
if not os.path.isfile(info_file):
|
| 56 |
-
util.printD("Creating model info for: " + filename)
|
| 57 |
-
# get model's sha256
|
| 58 |
-
hash = util.gen_file_sha256(item)
|
| 59 |
-
|
| 60 |
-
if not hash:
|
| 61 |
-
output = "failed generating SHA256 for model:" + filename
|
| 62 |
-
util.printD(output)
|
| 63 |
-
return output
|
| 64 |
-
|
| 65 |
-
# use this sha256 to get model info from civitai
|
| 66 |
-
model_info = civitai.get_model_info_by_hash(hash)
|
| 67 |
-
# delay 1 second for ti
|
| 68 |
-
if model_type == "ti":
|
| 69 |
-
util.printD("Delay 1 second for TI")
|
| 70 |
-
time.sleep(1)
|
| 71 |
-
|
| 72 |
-
if model_info is None:
|
| 73 |
-
output = "Connect to Civitai API service failed. Wait a while and try again"
|
| 74 |
-
util.printD(output)
|
| 75 |
-
return output+", check console log for detail"
|
| 76 |
-
|
| 77 |
-
# write model info to file
|
| 78 |
-
model.write_model_info(info_file, model_info)
|
| 79 |
-
|
| 80 |
-
# set model_count
|
| 81 |
-
model_count = model_count+1
|
| 82 |
-
|
| 83 |
-
# check preview image
|
| 84 |
-
civitai.get_preview_image_by_model_path(item, max_size_preview, skip_nsfw_preview)
|
| 85 |
-
image_count = image_count+1
|
| 86 |
-
|
| 87 |
-
|
| 88 |
-
# scan_log = "Done"
|
| 89 |
-
|
| 90 |
-
output = f"Done. Scanned {model_count} models, checked {image_count} images"
|
| 91 |
|
| 92 |
-
|
| 93 |
|
| 94 |
-
|
|
|
|
|
|
|
|
|
|
| 95 |
|
|
|
|
| 96 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 97 |
|
| 98 |
-
#
|
| 99 |
-
|
| 100 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 101 |
output = ""
|
|
|
|
|
|
|
|
|
|
| 102 |
# parse model id
|
| 103 |
model_id = civitai.get_model_id_from_url(model_url_or_id)
|
| 104 |
if not model_id:
|
| 105 |
-
output = "failed to parse model id from url:
|
| 106 |
util.printD(output)
|
| 107 |
-
|
|
|
|
| 108 |
|
| 109 |
# get model file path
|
| 110 |
# model could be in subfolder
|
| 111 |
-
|
| 112 |
-
|
| 113 |
-
|
| 114 |
-
|
| 115 |
-
return output
|
| 116 |
-
|
| 117 |
-
model_root, model_path = result
|
| 118 |
-
if not model_path:
|
| 119 |
-
output = "model path is empty"
|
| 120 |
util.printD(output)
|
| 121 |
-
|
| 122 |
-
|
| 123 |
-
# get info file path
|
| 124 |
-
base, ext = os.path.splitext(model_path)
|
| 125 |
-
info_file = base + civitai.suffix + model.info_ext
|
| 126 |
|
| 127 |
-
# get model info
|
| 128 |
#we call it model_info, but in civitai, it is actually version info
|
| 129 |
model_info = civitai.get_version_info_by_model_id(model_id)
|
| 130 |
|
| 131 |
-
|
| 132 |
-
output = "failed to get model info from url: " + model_url_or_id
|
| 133 |
-
util.printD(output)
|
| 134 |
-
return output
|
| 135 |
-
|
| 136 |
-
# write model info to file
|
| 137 |
-
model.write_model_info(info_file, model_info)
|
| 138 |
|
| 139 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 140 |
|
| 141 |
-
|
| 142 |
-
civitai.get_preview_image_by_model_path(model_path, max_size_preview, skip_nsfw_preview)
|
| 143 |
|
| 144 |
-
output = "Model Info saved to: " + info_file
|
| 145 |
-
return output
|
| 146 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 147 |
|
| 148 |
|
| 149 |
-
# check models' new version and output to UI as markdown doc
|
| 150 |
def check_models_new_version_to_md(model_types:list) -> str:
|
| 151 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 152 |
|
| 153 |
-
count = 0
|
| 154 |
-
output = ""
|
| 155 |
if not new_versions:
|
| 156 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 157 |
else:
|
| 158 |
-
|
| 159 |
-
for new_version in new_versions:
|
| 160 |
-
count = count+1
|
| 161 |
-
model_path, model_id, model_name, new_verion_id, new_version_name, description, download_url, img_url = new_version
|
| 162 |
-
# in md, each part is something like this:
|
| 163 |
-
# [model_name](model_url)
|
| 164 |
-
# [version_name](download_url)
|
| 165 |
-
# version description
|
| 166 |
-
url = civitai.url_dict["modelPage"]+str(model_id)
|
| 167 |
-
|
| 168 |
-
part = f'<div style="font-size:20px;margin:6px 0px;"><b>Model: <a href="{url}" target="_blank"><u>{model_name}</u></a></b></div>'
|
| 169 |
-
part = part + f'<div style="font-size:16px">File: {model_path}</div>'
|
| 170 |
-
if download_url:
|
| 171 |
-
# replace "\" to "/" in model_path for windows
|
| 172 |
-
model_path = model_path.replace('\\', '\\\\')
|
| 173 |
-
part = part + f'<div style="font-size:16px;margin:6px 0px;">New Version: <u><a href="{download_url}" target="_blank" style="margin:0px 10px;">{new_version_name}</a></u>'
|
| 174 |
-
# add js function to download new version into SD webui by python
|
| 175 |
-
part = part + " "
|
| 176 |
-
# in embed HTML, onclick= will also follow a ", never a ', so have to write it as following
|
| 177 |
-
part = part + f"<u><a href='#' style='margin:0px 10px;' onclick=\"ch_dl_model_new_version(event, '{model_path}', '{new_verion_id}', '{download_url}')\">[Download into SD]</a></u>"
|
| 178 |
-
|
| 179 |
-
else:
|
| 180 |
-
part = part + f'<div style="font-size:16px;margin:6px 0px;">New Version: {new_version_name}'
|
| 181 |
-
part = part + '</div>'
|
| 182 |
|
| 183 |
-
|
| 184 |
-
if description:
|
| 185 |
-
part = part + '<blockquote style="font-size:16px;margin:6px 0px;">'+ description + '</blockquote><br>'
|
| 186 |
|
| 187 |
-
# preview image
|
| 188 |
-
if img_url:
|
| 189 |
-
part = part + f"<img src='{img_url}'><br>"
|
| 190 |
-
|
| 191 |
|
| 192 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 193 |
|
| 194 |
-
|
|
|
|
|
|
|
| 195 |
|
| 196 |
-
|
|
|
|
|
|
|
| 197 |
|
|
|
|
|
|
|
| 198 |
|
| 199 |
-
#
|
| 200 |
-
|
| 201 |
-
|
| 202 |
|
| 203 |
-
|
| 204 |
-
|
| 205 |
-
|
| 206 |
-
util.printD("failed to parse model id from url or id")
|
| 207 |
-
return
|
| 208 |
|
| 209 |
-
|
| 210 |
-
if model_info is None:
|
| 211 |
-
util.printD("Connect to Civitai API service failed. Wait a while and try again")
|
| 212 |
-
return
|
| 213 |
-
|
| 214 |
-
if not model_info:
|
| 215 |
-
util.printD("failed to get model info from url or id")
|
| 216 |
-
return
|
| 217 |
-
|
| 218 |
-
# parse model type, model name, subfolder, version from this model info
|
| 219 |
-
# get model type
|
| 220 |
-
if "type" not in model_info.keys():
|
| 221 |
-
util.printD("model type is not in model_info")
|
| 222 |
-
return
|
| 223 |
-
|
| 224 |
-
civitai_model_type = model_info["type"]
|
| 225 |
-
if civitai_model_type not in civitai.model_type_dict.keys():
|
| 226 |
-
util.printD("This model type is not supported:"+civitai_model_type)
|
| 227 |
-
return
|
| 228 |
-
|
| 229 |
-
model_type = civitai.model_type_dict[civitai_model_type]
|
| 230 |
|
| 231 |
-
|
| 232 |
-
|
| 233 |
-
util.printD("model name is not in model_info")
|
| 234 |
-
return
|
| 235 |
|
| 236 |
-
|
| 237 |
-
|
| 238 |
-
util.printD("model name is Empty")
|
| 239 |
-
model_name = ""
|
| 240 |
|
| 241 |
-
|
| 242 |
-
|
| 243 |
-
|
| 244 |
-
|
| 245 |
-
|
| 246 |
-
|
| 247 |
-
if not modelVersions:
|
| 248 |
-
util.printD("modelVersions is Empty")
|
| 249 |
-
return
|
| 250 |
-
|
| 251 |
version_strs = []
|
| 252 |
-
|
|
|
|
| 253 |
# version name can not be used as id
|
| 254 |
# version id is not readable
|
| 255 |
# so , we use name_id as version string
|
| 256 |
-
version_str = version["name"]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 257 |
version_strs.append(version_str)
|
|
|
|
| 258 |
|
| 259 |
# get folder by model type
|
| 260 |
folder = model.folders[model_type]
|
| 261 |
-
# get subfolders
|
| 262 |
-
subfolders = util.get_subfolders(folder)
|
| 263 |
-
if not subfolders:
|
| 264 |
-
subfolders = []
|
| 265 |
|
| 266 |
-
#
|
| 267 |
-
subfolders
|
| 268 |
-
|
| 269 |
-
util.
|
| 270 |
-
|
| 271 |
-
|
| 272 |
-
|
| 273 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 274 |
|
| 275 |
-
return (model_info, model_name, model_type, subfolders, version_strs)
|
| 276 |
|
| 277 |
-
# get version info by version string
|
| 278 |
def get_ver_info_by_ver_str(version_str:str, model_info:dict) -> dict:
|
| 279 |
-
|
| 280 |
-
|
| 281 |
-
|
| 282 |
-
|
| 283 |
-
|
| 284 |
-
|
| 285 |
-
|
| 286 |
-
|
| 287 |
-
|
| 288 |
-
|
| 289 |
-
|
| 290 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 291 |
|
| 292 |
-
|
| 293 |
-
|
|
|
|
| 294 |
util.printD("modelVersions is Empty")
|
| 295 |
-
return
|
| 296 |
-
|
| 297 |
# find version by version_str
|
| 298 |
version = None
|
| 299 |
-
for ver in
|
| 300 |
# version name can not be used as id
|
| 301 |
# version id is not readable
|
| 302 |
# so , we use name_id as version string
|
| 303 |
-
ver_str = ver["name"]
|
| 304 |
if ver_str == version_str:
|
| 305 |
# find version
|
| 306 |
version = ver
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 307 |
|
| 308 |
-
if not version:
|
| 309 |
-
util.printD("can not find version by version string: " + version_str)
|
| 310 |
-
return
|
| 311 |
-
|
| 312 |
-
# get version id
|
| 313 |
-
if "id" not in version.keys():
|
| 314 |
-
util.printD("this version has no id")
|
| 315 |
-
return
|
| 316 |
-
|
| 317 |
return version
|
| 318 |
|
| 319 |
|
| 320 |
-
# get download url from model info by version string
|
| 321 |
-
# return - (version_id, download_url)
|
| 322 |
def get_id_and_dl_url_by_version_str(version_str:str, model_info:dict) -> tuple:
|
| 323 |
-
|
| 324 |
-
|
| 325 |
-
|
| 326 |
-
|
| 327 |
-
if not model_info:
|
| 328 |
-
util.
|
| 329 |
-
|
| 330 |
-
|
| 331 |
-
|
| 332 |
-
|
| 333 |
-
util.printD(
|
| 334 |
-
return
|
| 335 |
|
| 336 |
-
|
| 337 |
-
|
|
|
|
| 338 |
util.printD("modelVersions is Empty")
|
| 339 |
-
return
|
| 340 |
-
|
| 341 |
# find version by version_str
|
| 342 |
version = None
|
| 343 |
-
for ver in
|
| 344 |
# version name can not be used as id
|
| 345 |
# version id is not readable
|
| 346 |
# so , we use name_id as version string
|
| 347 |
-
ver_str = ver["name"]
|
| 348 |
if ver_str == version_str:
|
| 349 |
# find version
|
| 350 |
version = ver
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 351 |
|
| 352 |
-
|
| 353 |
-
util.printD("can not find version by version string: " + version_str)
|
| 354 |
-
return
|
| 355 |
-
|
| 356 |
-
# get version id
|
| 357 |
-
if "id" not in version.keys():
|
| 358 |
-
util.printD("this version has no id")
|
| 359 |
-
return
|
| 360 |
-
|
| 361 |
-
version_id = version["id"]
|
| 362 |
-
if not version_id:
|
| 363 |
-
util.printD("version id is Empty")
|
| 364 |
-
return
|
| 365 |
|
| 366 |
-
|
| 367 |
-
if "downloadUrl" not in version.keys():
|
| 368 |
-
util.printD("downloadUrl is not in this version")
|
| 369 |
-
return
|
| 370 |
-
|
| 371 |
-
downloadUrl = version["downloadUrl"]
|
| 372 |
-
if not downloadUrl:
|
| 373 |
-
util.printD("downloadUrl is Empty")
|
| 374 |
-
return
|
| 375 |
-
|
| 376 |
-
util.printD("Get Download Url: " + downloadUrl)
|
| 377 |
-
|
| 378 |
-
return (version_id, downloadUrl)
|
| 379 |
-
|
| 380 |
|
| 381 |
-
|
| 382 |
-
|
| 383 |
-
|
|
|
|
| 384 |
|
| 385 |
-
|
|
|
|
|
|
|
| 386 |
|
| 387 |
-
|
| 388 |
-
|
| 389 |
-
|
| 390 |
-
|
| 391 |
-
|
| 392 |
-
|
| 393 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 394 |
util.printD(output)
|
| 395 |
-
|
| 396 |
-
|
| 397 |
-
|
| 398 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 399 |
util.printD(output)
|
| 400 |
-
|
| 401 |
-
|
| 402 |
-
|
| 403 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 404 |
util.printD(output)
|
| 405 |
-
|
| 406 |
-
|
|
|
|
| 407 |
# get model root folder
|
| 408 |
-
if model_type not in model.folders
|
| 409 |
-
output = "
|
| 410 |
util.printD(output)
|
| 411 |
-
|
| 412 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 413 |
model_root_folder = model.folders[model_type]
|
| 414 |
|
|
|
|
|
|
|
|
|
|
| 415 |
|
| 416 |
# get subfolder
|
| 417 |
-
|
| 418 |
-
if subfolder_str == "/" or subfolder_str == "\\":
|
| 419 |
subfolder = ""
|
| 420 |
-
elif subfolder_str[:1]
|
| 421 |
subfolder = subfolder_str[1:]
|
| 422 |
else:
|
| 423 |
subfolder = subfolder_str
|
| 424 |
|
| 425 |
# get model folder for downloading
|
| 426 |
-
|
| 427 |
-
if not os.path.isdir(
|
| 428 |
-
output = "Model folder is not a dir: "
|
| 429 |
util.printD(output)
|
| 430 |
-
|
| 431 |
-
|
|
|
|
| 432 |
# get version info
|
| 433 |
ver_info = get_ver_info_by_ver_str(version_str, model_info)
|
| 434 |
if not ver_info:
|
| 435 |
-
output = "
|
| 436 |
util.printD(output)
|
| 437 |
-
|
| 438 |
-
|
| 439 |
-
version_id = ver_info["id"]
|
| 440 |
-
|
| 441 |
|
| 442 |
-
|
| 443 |
-
|
| 444 |
-
|
| 445 |
-
|
| 446 |
-
|
| 447 |
-
|
| 448 |
-
|
| 449 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 450 |
|
| 451 |
-
|
| 452 |
-
if "downloadUrl" in ver_info.keys():
|
| 453 |
-
download_urls.append(ver_info["downloadUrl"])
|
| 454 |
|
|
|
|
| 455 |
|
| 456 |
-
|
| 457 |
-
|
| 458 |
-
|
| 459 |
-
output = "This model version is already existed"
|
| 460 |
-
util.printD(output)
|
| 461 |
-
return output
|
| 462 |
-
|
| 463 |
-
# download
|
| 464 |
-
filepath = ""
|
| 465 |
-
for url in download_urls:
|
| 466 |
-
model_filepath = downloader.dl(url, model_folder, None, None)
|
| 467 |
-
if not model_filepath:
|
| 468 |
-
output = "Downloading failed, check console log for detail"
|
| 469 |
-
util.printD(output)
|
| 470 |
-
return output
|
| 471 |
-
|
| 472 |
-
if url == ver_info["downloadUrl"]:
|
| 473 |
-
filepath = model_filepath
|
| 474 |
-
else:
|
| 475 |
-
# only download one file
|
| 476 |
-
# get download url
|
| 477 |
-
url = ver_info["downloadUrl"]
|
| 478 |
-
if not url:
|
| 479 |
-
output = "Fail to get download url, check console log for detail"
|
| 480 |
-
util.printD(output)
|
| 481 |
-
return output
|
| 482 |
-
|
| 483 |
-
# download
|
| 484 |
-
filepath = downloader.dl(url, model_folder, None, None)
|
| 485 |
-
if not filepath:
|
| 486 |
-
output = "Downloading failed, check console log for detail"
|
| 487 |
-
util.printD(output)
|
| 488 |
-
return output
|
| 489 |
-
|
| 490 |
|
| 491 |
-
if not filepath:
|
| 492 |
-
filepath = model_filepath
|
| 493 |
-
|
| 494 |
# get version info
|
| 495 |
-
version_info = civitai.get_version_info_by_version_id(
|
| 496 |
-
|
| 497 |
-
|
| 498 |
-
|
| 499 |
-
|
| 500 |
-
|
| 501 |
-
|
| 502 |
-
|
| 503 |
-
|
| 504 |
-
|
| 505 |
-
|
| 506 |
-
|
| 507 |
-
|
| 508 |
-
|
| 509 |
-
|
| 510 |
util.printD(output)
|
| 511 |
-
|
|
|
|
| 1 |
+
""" -*- coding: UTF-8 -*-
|
| 2 |
+
handle msg between js and python side
|
| 3 |
+
"""
|
| 4 |
import os
|
| 5 |
import time
|
| 6 |
+
import re
|
| 7 |
+
import gradio as gr
|
| 8 |
+
from modules import sd_models
|
| 9 |
from . import util
|
| 10 |
from . import model
|
| 11 |
from . import civitai
|
| 12 |
from . import downloader
|
| 13 |
+
from . import templates
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
def get_metadata_skeleton():
|
| 17 |
+
"""
|
| 18 |
+
Used to generate at least something when model is not on civitai.
|
| 19 |
+
"""
|
| 20 |
+
metadata = {
|
| 21 |
+
"id": "",
|
| 22 |
+
"modelId": "",
|
| 23 |
+
"name": "",
|
| 24 |
+
"trainedWords": [],
|
| 25 |
+
"baseModel": "Unknown",
|
| 26 |
+
"description": "",
|
| 27 |
+
"model": {
|
| 28 |
+
"name": "",
|
| 29 |
+
"type": "",
|
| 30 |
+
"nsfw": "",
|
| 31 |
+
"poi": ""
|
| 32 |
+
},
|
| 33 |
+
"files": [
|
| 34 |
+
{
|
| 35 |
+
"name": "",
|
| 36 |
+
"sizeKB": 0,
|
| 37 |
+
"type": "Model",
|
| 38 |
+
"hashes": {
|
| 39 |
+
"AutoV2": "",
|
| 40 |
+
"SHA256": ""
|
| 41 |
+
}
|
| 42 |
+
}
|
| 43 |
+
],
|
| 44 |
+
"tags": [],
|
| 45 |
+
"downloadUrl": "",
|
| 46 |
+
"skeleton_file": True
|
| 47 |
+
}
|
| 48 |
+
|
| 49 |
+
return metadata
|
| 50 |
+
|
| 51 |
+
|
| 52 |
+
def scan_single_model(filepath, model_type, refetch_old, delay):
|
| 53 |
+
"""
|
| 54 |
+
Gets model info for a model by feeding its sha256 hash into civitai's api
|
| 55 |
+
|
| 56 |
+
return: success:bool
|
| 57 |
+
"""
|
| 58 |
+
|
| 59 |
+
filename = os.path.basename(filepath)
|
| 60 |
+
|
| 61 |
+
# find a model, get info file
|
| 62 |
+
info_file, sd15_file = model.get_model_info_paths(filepath)
|
| 63 |
|
| 64 |
+
output = ""
|
| 65 |
+
|
| 66 |
+
# check info file
|
| 67 |
+
if model.metadata_needed(info_file, sd15_file, refetch_old):
|
| 68 |
+
output = f"Creating model info for: {filename}"
|
| 69 |
+
util.printD(output)
|
| 70 |
+
yield output
|
| 71 |
+
|
| 72 |
+
# get model's sha256
|
| 73 |
+
result = None
|
| 74 |
+
for result in util.gen_file_sha256(filepath):
|
| 75 |
+
if isinstance(result, tuple):
|
| 76 |
+
yield result
|
| 77 |
+
sha256_hash = result
|
| 78 |
+
|
| 79 |
+
util.printD(f"model action sha256: {sha256_hash}")
|
| 80 |
+
|
| 81 |
+
if not sha256_hash:
|
| 82 |
+
output = f"failed generating SHA256 for model: {filename}"
|
| 83 |
+
util.printD(output)
|
| 84 |
+
yield output
|
| 85 |
+
time.sleep(delay)
|
| 86 |
+
yield False
|
| 87 |
+
|
| 88 |
+
yield "Requesting model information from Civitai"
|
| 89 |
+
# use this sha256 to get model info from civitai
|
| 90 |
+
model_info = civitai.get_model_info_by_hash(sha256_hash)
|
| 91 |
+
|
| 92 |
+
if not model_info:
|
| 93 |
+
model_info = dummy_model_info(filepath, sha256_hash, model_type)
|
| 94 |
+
|
| 95 |
+
model.process_model_info(filepath, model_info, model_type, refetch_old=refetch_old)
|
| 96 |
+
|
| 97 |
+
# delay before next request, to prevent being treated as a DDoS attack
|
| 98 |
+
time.sleep(delay)
|
| 99 |
+
|
| 100 |
+
else:
|
| 101 |
+
util.printD(f"Model metadata not needed for {filename}")
|
| 102 |
+
|
| 103 |
+
yield True
|
| 104 |
+
|
| 105 |
+
|
| 106 |
+
def scan_model(scan_model_types, nsfw_preview_threshold, refetch_old, progress=gr.Progress()):
|
| 107 |
+
""" Scan model to generate SHA256, then use this SHA256 to get model info from civitai
|
| 108 |
+
return output msg
|
| 109 |
+
"""
|
| 110 |
+
|
| 111 |
+
delay = 0.2
|
| 112 |
|
|
|
|
|
|
|
|
|
|
| 113 |
util.printD("Start scan_model")
|
| 114 |
output = ""
|
| 115 |
|
| 116 |
+
max_size_preview = util.get_opts("ch_max_size_preview")
|
| 117 |
+
|
| 118 |
# check model types
|
| 119 |
if not scan_model_types:
|
| 120 |
output = "Model Types is None, can not scan."
|
| 121 |
util.printD(output)
|
| 122 |
+
yield output
|
| 123 |
+
return
|
| 124 |
+
|
| 125 |
+
model_types = scan_model_types
|
| 126 |
+
if isinstance(scan_model_types, str):
|
| 127 |
+
# check if type is a string
|
| 128 |
+
model_types = [scan_model_types]
|
| 129 |
+
|
| 130 |
+
models = []
|
|
|
|
|
|
|
|
|
|
| 131 |
for model_type, model_folder in model.folders.items():
|
| 132 |
if model_type not in model_types:
|
| 133 |
continue
|
| 134 |
|
| 135 |
+
util.printD(f"Scanning path: {model_folder}")
|
| 136 |
+
for root, _, files in os.walk(model_folder, followlinks=True):
|
| 137 |
for filename in files:
|
| 138 |
+
|
| 139 |
# check ext
|
| 140 |
+
filepath = os.path.join(root, filename)
|
| 141 |
+
_, ext = os.path.splitext(filepath)
|
| 142 |
+
if ext not in model.EXTS:
|
| 143 |
+
continue
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 144 |
|
| 145 |
+
models.append((filepath, model_type))
|
| 146 |
|
| 147 |
+
count = [0, 0]
|
| 148 |
+
total = len(models)
|
| 149 |
+
for filepath, model_type in models:
|
| 150 |
+
success = None
|
| 151 |
|
| 152 |
+
tracker = (count[0], total)
|
| 153 |
|
| 154 |
+
progress(
|
| 155 |
+
tracker,
|
| 156 |
+
desc="Scanning...",
|
| 157 |
+
unit="models"
|
| 158 |
+
)
|
| 159 |
+
|
| 160 |
+
count[0] = count[0] + 1
|
| 161 |
+
|
| 162 |
+
for result in scan_single_model(filepath, model_type, refetch_old, delay):
|
| 163 |
+
if isinstance(result, str):
|
| 164 |
+
progress(tracker, desc=result, unit="models")
|
| 165 |
+
continue
|
| 166 |
+
if isinstance(result, tuple):
|
| 167 |
+
percent, status = result
|
| 168 |
+
progress(percent, desc=status)
|
| 169 |
+
continue
|
| 170 |
+
|
| 171 |
+
success = result
|
| 172 |
+
break
|
| 173 |
+
|
| 174 |
+
if not success:
|
| 175 |
+
continue
|
| 176 |
|
| 177 |
+
# set model_count
|
| 178 |
+
count[1] = count[1] + 1
|
| 179 |
+
|
| 180 |
+
# check preview image
|
| 181 |
+
for _ in civitai.get_preview_image_by_model_path(
|
| 182 |
+
filepath,
|
| 183 |
+
max_size_preview,
|
| 184 |
+
nsfw_preview_threshold
|
| 185 |
+
):
|
| 186 |
+
pass
|
| 187 |
+
|
| 188 |
+
# this previously had an image count, but it always matched the model count.
|
| 189 |
+
output = f"Done. Successfully scanned {count[1]} of {len(models)} models."
|
| 190 |
+
|
| 191 |
+
util.printD(output)
|
| 192 |
+
|
| 193 |
+
yield output
|
| 194 |
+
|
| 195 |
+
|
| 196 |
+
def dummy_model_info(path, sha256_hash, model_type):
|
| 197 |
+
"""
|
| 198 |
+
Fills model metadata with information we can get locally.
|
| 199 |
+
"""
|
| 200 |
+
if not sha256_hash:
|
| 201 |
+
return {}
|
| 202 |
+
|
| 203 |
+
model_info = get_metadata_skeleton()
|
| 204 |
+
|
| 205 |
+
autov2 = sha256_hash[:10]
|
| 206 |
+
filename = os.path.basename(path)
|
| 207 |
+
filesize = os.path.getsize(path) // 1024
|
| 208 |
+
|
| 209 |
+
model_metadata = model_info["model"]
|
| 210 |
+
file_metadata = model_info["files"][0]
|
| 211 |
+
|
| 212 |
+
model_metadata["name"] = filename
|
| 213 |
+
model_metadata["type"] = model_type
|
| 214 |
+
|
| 215 |
+
file_metadata["name"] = filename
|
| 216 |
+
file_metadata["sizeKB"] = filesize
|
| 217 |
+
file_metadata["hashes"]["SHA256"] = sha256_hash
|
| 218 |
+
file_metadata["hashes"]["AutoV2"] = autov2
|
| 219 |
+
|
| 220 |
+
# We can't get data on the model from civitai, but some models
|
| 221 |
+
# do store their training data.
|
| 222 |
+
trained_words = model_info["trainedWords"]
|
| 223 |
+
tags = model_info["tags"]
|
| 224 |
+
|
| 225 |
+
try:
|
| 226 |
+
file_metadata = sd_models.read_metadata_from_safetensors(path)
|
| 227 |
+
except AssertionError:
|
| 228 |
+
# model is not a safetensors file. This is fine,
|
| 229 |
+
# it just doesn't have metadata we can read
|
| 230 |
+
pass
|
| 231 |
+
|
| 232 |
+
tag_frequency = file_metadata.get("ss_tag_frequency", {})
|
| 233 |
+
|
| 234 |
+
for trained_word in tag_frequency.keys():
|
| 235 |
+
# kohya training scripts use
|
| 236 |
+
# `{iterations}_{trained_word}`
|
| 237 |
+
# for training finetune concepts.
|
| 238 |
+
word = re.sub(r"^\d+_", "", trained_word)
|
| 239 |
+
trained_words.append(word)
|
| 240 |
+
|
| 241 |
+
# "tags" in this case are just words used in image captions
|
| 242 |
+
# when training the finetune model.
|
| 243 |
+
# They may or may not be useful for prompting
|
| 244 |
+
for tag in tag_frequency[trained_word].keys():
|
| 245 |
+
tag = tag.replace(",", "").strip()
|
| 246 |
+
if tag == "":
|
| 247 |
+
continue
|
| 248 |
+
tags.append(tag)
|
| 249 |
+
|
| 250 |
+
return model_info
|
| 251 |
+
|
| 252 |
+
|
| 253 |
+
def get_model_info_by_input(
|
| 254 |
+
model_type, model_name, model_url_or_id, nsfw_preview_threshold
|
| 255 |
+
):
|
| 256 |
+
"""
|
| 257 |
+
Get model info by model type, name and url
|
| 258 |
+
output is log info to display on markdown component
|
| 259 |
+
"""
|
| 260 |
output = ""
|
| 261 |
+
|
| 262 |
+
max_size_preview = util.get_opts("ch_max_size_preview")
|
| 263 |
+
|
| 264 |
# parse model id
|
| 265 |
model_id = civitai.get_model_id_from_url(model_url_or_id)
|
| 266 |
if not model_id:
|
| 267 |
+
output = f"failed to parse model id from url: {model_url_or_id}"
|
| 268 |
util.printD(output)
|
| 269 |
+
yield output
|
| 270 |
+
return
|
| 271 |
|
| 272 |
# get model file path
|
| 273 |
# model could be in subfolder
|
| 274 |
+
model_path = model.get_model_path_by_type_and_name(model_type, model_name)
|
| 275 |
+
|
| 276 |
+
if model_path is None:
|
| 277 |
+
output = "Could not get Model Path"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 278 |
util.printD(output)
|
| 279 |
+
yield output
|
| 280 |
+
return
|
|
|
|
|
|
|
|
|
|
| 281 |
|
| 282 |
+
# get model info
|
| 283 |
#we call it model_info, but in civitai, it is actually version info
|
| 284 |
model_info = civitai.get_version_info_by_model_id(model_id)
|
| 285 |
|
| 286 |
+
model.process_model_info(model_path, model_info, model_type)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 287 |
|
| 288 |
+
# check preview image + webui-visible progress bar
|
| 289 |
+
yield from civitai.get_preview_image_by_model_path(
|
| 290 |
+
model_path,
|
| 291 |
+
max_size_preview,
|
| 292 |
+
nsfw_preview_threshold
|
| 293 |
+
)
|
| 294 |
|
| 295 |
+
yield output
|
|
|
|
| 296 |
|
|
|
|
|
|
|
| 297 |
|
| 298 |
+
def build_article_from_version(version):
|
| 299 |
+
"""
|
| 300 |
+
Builds the HTML for displaying new model versions to the user.
|
| 301 |
+
|
| 302 |
+
return: html:str
|
| 303 |
+
"""
|
| 304 |
+
(
|
| 305 |
+
model_path, model_id, model_name, new_version_id,
|
| 306 |
+
new_version_name, description, download_url,
|
| 307 |
+
img_url, model_type
|
| 308 |
+
) = version
|
| 309 |
+
|
| 310 |
+
thumbnail = ""
|
| 311 |
+
if img_url:
|
| 312 |
+
thumbnail = templates.thumbnail.substitute(
|
| 313 |
+
img_url=img_url,
|
| 314 |
+
)
|
| 315 |
+
|
| 316 |
+
if download_url:
|
| 317 |
+
# replace "\" to "/" in model_path for windows
|
| 318 |
+
download_model_path = model_path.replace('\\', '\\\\')
|
| 319 |
+
|
| 320 |
+
download_section = templates.download.substitute(
|
| 321 |
+
new_version_id=new_version_id,
|
| 322 |
+
new_version_name=new_version_name,
|
| 323 |
+
model_path=download_model_path,
|
| 324 |
+
model_type=model_type,
|
| 325 |
+
download_url=download_url
|
| 326 |
+
)
|
| 327 |
+
|
| 328 |
+
else:
|
| 329 |
+
download_section = templates.no_download.substitute(
|
| 330 |
+
new_version_name=new_version_name,
|
| 331 |
+
)
|
| 332 |
+
|
| 333 |
+
description_section = ""
|
| 334 |
+
if description:
|
| 335 |
+
description_section = templates.description.substitute(
|
| 336 |
+
description=util.safe_html(download_section),
|
| 337 |
+
)
|
| 338 |
+
|
| 339 |
+
article = templates.article.substitute(
|
| 340 |
+
url=f'{civitai.URLS["modelPage"]}{model_id}',
|
| 341 |
+
thumbnail=thumbnail,
|
| 342 |
+
download=download_section,
|
| 343 |
+
description=description_section,
|
| 344 |
+
model_name=model_name,
|
| 345 |
+
model_path=model_path
|
| 346 |
+
)
|
| 347 |
+
|
| 348 |
+
return article
|
| 349 |
|
| 350 |
|
|
|
|
| 351 |
def check_models_new_version_to_md(model_types:list) -> str:
|
| 352 |
+
"""
|
| 353 |
+
check models' new version and output to UI as html doc
|
| 354 |
+
return: html:str
|
| 355 |
+
"""
|
| 356 |
+
new_versions = civitai.check_models_new_version_by_model_types(model_types, 0.2)
|
| 357 |
|
|
|
|
|
|
|
| 358 |
if not new_versions:
|
| 359 |
+
util.printD("Done: no new versions found.")
|
| 360 |
+
return "No models have new versions"
|
| 361 |
+
|
| 362 |
+
articles = []
|
| 363 |
+
count = 0
|
| 364 |
+
for count, new_version in enumerate(new_versions):
|
| 365 |
+
article = build_article_from_version(new_version)
|
| 366 |
+
articles.append(article)
|
| 367 |
+
|
| 368 |
+
output = f"Found new versions for following models: <section>{''.join(articles)}</section>"
|
| 369 |
+
|
| 370 |
+
if count != 1:
|
| 371 |
+
util.printD(f"Done. Found {count} models that have new versions. Check UI for detail")
|
| 372 |
else:
|
| 373 |
+
util.printD(f"Done. Found {count} model that has a new version. Check UI for detail.")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 374 |
|
| 375 |
+
return output
|
|
|
|
|
|
|
| 376 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 377 |
|
| 378 |
+
def get_model_info_by_url(model_url_or_id:str) -> dict:
|
| 379 |
+
"""
|
| 380 |
+
Retrieves model information necessary to populate HTML
|
| 381 |
+
with Model Name, Model Type, valid saving directories,
|
| 382 |
+
and available model versions.
|
| 383 |
|
| 384 |
+
return: tuple or None
|
| 385 |
+
"""
|
| 386 |
+
util.printD(f"Getting model info by: {model_url_or_id}")
|
| 387 |
|
| 388 |
+
try:
|
| 389 |
+
# parse model id
|
| 390 |
+
model_id = civitai.get_model_id_from_url(model_url_or_id)
|
| 391 |
|
| 392 |
+
# download model info
|
| 393 |
+
model_info = civitai.get_model_info_by_id(model_id)
|
| 394 |
|
| 395 |
+
# parse model type, model name, subfolder, version from this model info
|
| 396 |
+
# get model type
|
| 397 |
+
civitai_model_type = model_info["type"]
|
| 398 |
|
| 399 |
+
if civitai_model_type not in civitai.MODEL_TYPES:
|
| 400 |
+
util.printD(f"This model type is not supported: {civitai_model_type}")
|
| 401 |
+
return None
|
|
|
|
|
|
|
| 402 |
|
| 403 |
+
model_type = civitai.MODEL_TYPES[civitai_model_type]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 404 |
|
| 405 |
+
# get model type
|
| 406 |
+
model_name = model_info["name"]
|
|
|
|
|
|
|
| 407 |
|
| 408 |
+
# get version lists
|
| 409 |
+
model_versions = model_info["modelVersions"]
|
|
|
|
|
|
|
| 410 |
|
| 411 |
+
except (KeyError, ValueError, TypeError) as e:
|
| 412 |
+
util.printD(f"An error occurred while attempting to process model info: \n\t{e}")
|
| 413 |
+
return None
|
| 414 |
+
|
| 415 |
+
filenames = []
|
| 416 |
+
files = []
|
|
|
|
|
|
|
|
|
|
|
|
|
| 417 |
version_strs = []
|
| 418 |
+
previews = {}
|
| 419 |
+
for version in model_versions:
|
| 420 |
# version name can not be used as id
|
| 421 |
# version id is not readable
|
| 422 |
# so , we use name_id as version string
|
| 423 |
+
version_str = f'{version["name"]}_{version["id"]}'
|
| 424 |
+
|
| 425 |
+
filename = ""
|
| 426 |
+
try:
|
| 427 |
+
for filedata in version["files"]:
|
| 428 |
+
if filedata["type"] == "Model":
|
| 429 |
+
filename = filedata["name"]
|
| 430 |
+
|
| 431 |
+
except (ValueError, KeyError):
|
| 432 |
+
pass
|
| 433 |
+
|
| 434 |
+
files.append(version["files"])
|
| 435 |
+
filenames.append(filename)
|
| 436 |
version_strs.append(version_str)
|
| 437 |
+
previews[version_str] = version["images"]
|
| 438 |
|
| 439 |
# get folder by model type
|
| 440 |
folder = model.folders[model_type]
|
|
|
|
|
|
|
|
|
|
|
|
|
| 441 |
|
| 442 |
+
# get subfolders
|
| 443 |
+
subfolders = ["/"] + util.get_subfolders(folder)
|
| 444 |
+
|
| 445 |
+
msg = util.indented_msg(f"""
|
| 446 |
+
Got following info for downloading:
|
| 447 |
+
{model_name=}
|
| 448 |
+
{model_type=}
|
| 449 |
+
{version_strs=}
|
| 450 |
+
{subfolders=}
|
| 451 |
+
{previews=}
|
| 452 |
+
""")
|
| 453 |
+
util.printD(msg)
|
| 454 |
+
|
| 455 |
+
return {
|
| 456 |
+
"model_info": model_info,
|
| 457 |
+
"model_name": model_name,
|
| 458 |
+
"model_type": model_type,
|
| 459 |
+
"files": files,
|
| 460 |
+
"filenames": filenames,
|
| 461 |
+
"subfolders": subfolders,
|
| 462 |
+
"version_strs": version_strs,
|
| 463 |
+
"previews": previews
|
| 464 |
+
}
|
| 465 |
|
|
|
|
| 466 |
|
|
|
|
| 467 |
def get_ver_info_by_ver_str(version_str:str, model_info:dict) -> dict:
|
| 468 |
+
"""
|
| 469 |
+
get version info by version string
|
| 470 |
+
|
| 471 |
+
return: version_info:dict
|
| 472 |
+
"""
|
| 473 |
+
|
| 474 |
+
if not (version_str and model_info):
|
| 475 |
+
output = util.indented_msg(
|
| 476 |
+
f"""
|
| 477 |
+
Missing Parameter:
|
| 478 |
+
{model_info=}
|
| 479 |
+
{version_str=}
|
| 480 |
+
"""
|
| 481 |
+
)
|
| 482 |
+
util.printD(output)
|
| 483 |
+
return None
|
| 484 |
|
| 485 |
+
# get version list
|
| 486 |
+
model_versions = model_info.get("modelVersions", None)
|
| 487 |
+
if model_versions is None:
|
| 488 |
util.printD("modelVersions is Empty")
|
| 489 |
+
return None
|
| 490 |
+
|
| 491 |
# find version by version_str
|
| 492 |
version = None
|
| 493 |
+
for ver in model_versions:
|
| 494 |
# version name can not be used as id
|
| 495 |
# version id is not readable
|
| 496 |
# so , we use name_id as version string
|
| 497 |
+
ver_str = f'{ver["name"]}_{ver["id"]}'
|
| 498 |
if ver_str == version_str:
|
| 499 |
# find version
|
| 500 |
version = ver
|
| 501 |
+
break
|
| 502 |
+
|
| 503 |
+
if not (version and ("id" in version)):
|
| 504 |
+
util.printD(f"can not find version or id by version string: {version_str}")
|
| 505 |
+
return None
|
| 506 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 507 |
return version
|
| 508 |
|
| 509 |
|
|
|
|
|
|
|
| 510 |
def get_id_and_dl_url_by_version_str(version_str:str, model_info:dict) -> tuple:
|
| 511 |
+
"""
|
| 512 |
+
get download url from model info by version string
|
| 513 |
+
return - (version_id, download_url)
|
| 514 |
+
"""
|
| 515 |
+
if not (version_str and model_info):
|
| 516 |
+
output = util.indented_msg(f"""
|
| 517 |
+
Missing Parameter:
|
| 518 |
+
{model_info=}
|
| 519 |
+
{version_str=}
|
| 520 |
+
""")
|
| 521 |
+
util.printD(output)
|
| 522 |
+
return (False, output)
|
| 523 |
|
| 524 |
+
# get version list
|
| 525 |
+
model_versions = model_info.get("modelVersions", None)
|
| 526 |
+
if model_versions is None:
|
| 527 |
util.printD("modelVersions is Empty")
|
| 528 |
+
return (False, output)
|
| 529 |
+
|
| 530 |
# find version by version_str
|
| 531 |
version = None
|
| 532 |
+
for ver in model_versions:
|
| 533 |
# version name can not be used as id
|
| 534 |
# version id is not readable
|
| 535 |
# so , we use name_id as version string
|
| 536 |
+
ver_str = f'{ver["name"]}_{ver["id"]}'
|
| 537 |
if ver_str == version_str:
|
| 538 |
# find version
|
| 539 |
version = ver
|
| 540 |
+
break
|
| 541 |
+
|
| 542 |
+
version_id = None
|
| 543 |
+
download_url = None
|
| 544 |
+
if version:
|
| 545 |
+
download_url = version.get("downloadUrl", None)
|
| 546 |
+
version_id = version.get("id", None)
|
| 547 |
+
|
| 548 |
+
if None in [version, version_id, download_url]:
|
| 549 |
+
output = util.indented_msg(f"""
|
| 550 |
+
Invalid Version Information:
|
| 551 |
+
{version=}
|
| 552 |
+
{version_id=}
|
| 553 |
+
{download_url=}
|
| 554 |
+
""")
|
| 555 |
+
util.printD(output)
|
| 556 |
+
return (False, output)
|
| 557 |
|
| 558 |
+
util.printD(f"Get Download Url: {download_url}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 559 |
|
| 560 |
+
return (version_id, download_url)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 561 |
|
| 562 |
+
def parse_file_info(file_info, basename):
|
| 563 |
+
"""
|
| 564 |
+
returns data required to download a file from civitai
|
| 565 |
+
"""
|
| 566 |
|
| 567 |
+
download_url = file_info.get("downloadUrl", None)
|
| 568 |
+
if download_url is None:
|
| 569 |
+
return None
|
| 570 |
|
| 571 |
+
filetype = file_info["type"]
|
| 572 |
+
filename = file_info["name"]
|
| 573 |
+
if basename and not filetype == "VAE":
|
| 574 |
+
filename = f"{basename}.{filename.split('.')[-1]}"
|
| 575 |
+
|
| 576 |
+
return {
|
| 577 |
+
"url": download_url,
|
| 578 |
+
"filename": filename,
|
| 579 |
+
"type": filetype
|
| 580 |
+
}
|
| 581 |
+
|
| 582 |
+
|
| 583 |
+
def download_files(filename, model_folder, ver_info, headers, filetypes, dl_all, duplicate):
|
| 584 |
+
"""
|
| 585 |
+
get download urls from files info
|
| 586 |
+
some model versions have multiple files
|
| 587 |
+
"""
|
| 588 |
+
|
| 589 |
+
version_id = ver_info["id"]
|
| 590 |
+
|
| 591 |
+
downloads = []
|
| 592 |
+
|
| 593 |
+
# check if this model already exists
|
| 594 |
+
result = civitai.search_local_model_info_by_version_id(model_folder, version_id)
|
| 595 |
+
if result:
|
| 596 |
+
output = "This model version already exists"
|
| 597 |
util.printD(output)
|
| 598 |
+
yield (False, output)
|
| 599 |
+
|
| 600 |
+
for file_info in ver_info.get("files", {}):
|
| 601 |
+
if not dl_all:
|
| 602 |
+
if not file_info["type"] in filetypes:
|
| 603 |
+
continue
|
| 604 |
+
|
| 605 |
+
dl_info = parse_file_info(file_info, filename)
|
| 606 |
+
|
| 607 |
+
if dl_info:
|
| 608 |
+
downloads.append(dl_info)
|
| 609 |
+
|
| 610 |
+
if len(downloads) == 0:
|
| 611 |
+
dl_info = parse_file_info(ver_info, filename)
|
| 612 |
+
if dl_info:
|
| 613 |
+
downloads.append(dl_info)
|
| 614 |
+
|
| 615 |
+
# download
|
| 616 |
+
success = False
|
| 617 |
+
output = ""
|
| 618 |
+
filepath = None
|
| 619 |
+
total = len(downloads)
|
| 620 |
+
errors = []
|
| 621 |
+
errors_count = 0
|
| 622 |
+
snippet = None
|
| 623 |
+
|
| 624 |
+
for count, dl_info in enumerate(downloads):
|
| 625 |
+
|
| 626 |
+
url = dl_info["url"]
|
| 627 |
+
if errors_count > 0:
|
| 628 |
+
snippet = f"{errors_count}/{total} files failed"
|
| 629 |
+
|
| 630 |
+
dl_folder = model_folder
|
| 631 |
+
if dl_info["type"] == "VAE":
|
| 632 |
+
dl_folder = model.vae_folder
|
| 633 |
+
|
| 634 |
+
# webui visible progress bar
|
| 635 |
+
for result in downloader.dl_file(
|
| 636 |
+
url, filename=dl_info["filename"], folder=dl_folder, duplicate=duplicate,
|
| 637 |
+
headers=headers
|
| 638 |
+
):
|
| 639 |
+
if not isinstance(result, str):
|
| 640 |
+
success, output = result
|
| 641 |
+
break
|
| 642 |
+
|
| 643 |
+
output = f"{result} | {count}/{total} files"
|
| 644 |
+
if snippet:
|
| 645 |
+
" | ".join([output, snippet])
|
| 646 |
+
|
| 647 |
+
yield output
|
| 648 |
+
|
| 649 |
+
if not success:
|
| 650 |
+
errors.append(downloader.error(url, output))
|
| 651 |
+
errors_count += 1
|
| 652 |
+
continue
|
| 653 |
+
|
| 654 |
+
if dl_info["type"] == "Model":
|
| 655 |
+
filepath = output
|
| 656 |
+
|
| 657 |
+
additional = None
|
| 658 |
+
if errors_count > 0:
|
| 659 |
+
additional = "\n\n".join(errors)
|
| 660 |
+
|
| 661 |
+
if errors_count == total:
|
| 662 |
+
yield (False, additional)
|
| 663 |
+
return
|
| 664 |
+
|
| 665 |
+
yield (True, filepath, additional)
|
| 666 |
+
|
| 667 |
+
|
| 668 |
+
def download_one(filename, model_folder, ver_info, headers, duplicate):
|
| 669 |
+
"""
|
| 670 |
+
only download one file
|
| 671 |
+
get download url
|
| 672 |
+
"""
|
| 673 |
+
|
| 674 |
+
download_url = ver_info["downloadUrl"]
|
| 675 |
+
|
| 676 |
+
output = ""
|
| 677 |
+
if not download_url:
|
| 678 |
+
output = "Failed to find a download url"
|
| 679 |
util.printD(output)
|
| 680 |
+
yield (False, output)
|
| 681 |
+
|
| 682 |
+
# download
|
| 683 |
+
success = False
|
| 684 |
+
for result in downloader.dl_file(
|
| 685 |
+
download_url, filename=filename, folder=model_folder,
|
| 686 |
+
duplicate=duplicate, headers=headers
|
| 687 |
+
):
|
| 688 |
+
if not isinstance(result, str):
|
| 689 |
+
success, output = result
|
| 690 |
+
break
|
| 691 |
+
|
| 692 |
+
yield result
|
| 693 |
+
|
| 694 |
+
if not success:
|
| 695 |
+
downloader.error(download_url, output)
|
| 696 |
+
yield (False, output)
|
| 697 |
+
|
| 698 |
+
yield (True, output)
|
| 699 |
+
|
| 700 |
+
|
| 701 |
+
def dl_model_by_input(
|
| 702 |
+
ch_state:dict,
|
| 703 |
+
model_type:str,
|
| 704 |
+
subfolder_str:str,
|
| 705 |
+
version_str:str,
|
| 706 |
+
filename:str,
|
| 707 |
+
file_ext:str,
|
| 708 |
+
dl_all:bool,
|
| 709 |
+
nsfw_preview_threshold:bool,
|
| 710 |
+
duplicate:str,
|
| 711 |
+
preview:str,
|
| 712 |
+
*args
|
| 713 |
+
) -> str:
|
| 714 |
+
""" download model from civitai by input
|
| 715 |
+
output to markdown log
|
| 716 |
+
"""
|
| 717 |
+
|
| 718 |
+
model_info = ch_state["model_info"]
|
| 719 |
+
max_size_preview = util.get_opts("ch_max_size_preview")
|
| 720 |
+
|
| 721 |
+
if not (model_info and model_type and subfolder_str and version_str):
|
| 722 |
+
output = util.indented_msg(f"""
|
| 723 |
+
Missing Required Parameter in dl_model_by_input. Parameters given:
|
| 724 |
+
{model_type=}*
|
| 725 |
+
{subfolder_str=}*
|
| 726 |
+
{version_str=}*
|
| 727 |
+
{filename=}
|
| 728 |
+
{file_ext=}
|
| 729 |
+
{max_size_preview=}
|
| 730 |
+
{nsfw_preview_threshold=}
|
| 731 |
+
{duplicate=}
|
| 732 |
+
""")
|
| 733 |
+
|
| 734 |
+
# Keep model info away from util.indented_msg
|
| 735 |
+
# which can screw with complex strings
|
| 736 |
+
output = f"{output}\n {model_info=}*\n * Required"
|
| 737 |
util.printD(output)
|
| 738 |
+
yield output
|
| 739 |
+
return
|
| 740 |
+
|
| 741 |
# get model root folder
|
| 742 |
+
if model_type not in model.folders:
|
| 743 |
+
output = f"Unsupported model type: {model_type}"
|
| 744 |
util.printD(output)
|
| 745 |
+
yield output
|
| 746 |
+
return
|
| 747 |
+
|
| 748 |
+
folder = ""
|
| 749 |
+
subfolder = ""
|
| 750 |
+
output = ""
|
| 751 |
+
version_info = None
|
| 752 |
+
|
| 753 |
+
filetypes = []
|
| 754 |
+
for filetype, will_dl in zip(civitai.FILE_TYPES, args):
|
| 755 |
+
if will_dl:
|
| 756 |
+
filetypes.append(filetype)
|
| 757 |
+
|
| 758 |
model_root_folder = model.folders[model_type]
|
| 759 |
|
| 760 |
+
if not os.path.exists(model_root_folder):
|
| 761 |
+
# Model directories may not exist by default
|
| 762 |
+
os.mkdir(model_root_folder)
|
| 763 |
|
| 764 |
# get subfolder
|
| 765 |
+
if subfolder_str in ["/", "\\"]:
|
|
|
|
| 766 |
subfolder = ""
|
| 767 |
+
elif subfolder_str[:1] in ["/", "\\"]:
|
| 768 |
subfolder = subfolder_str[1:]
|
| 769 |
else:
|
| 770 |
subfolder = subfolder_str
|
| 771 |
|
| 772 |
# get model folder for downloading
|
| 773 |
+
folder = os.path.join(model_root_folder, subfolder)
|
| 774 |
+
if not os.path.isdir(folder):
|
| 775 |
+
output = f"Model folder is not a dir: {folder}"
|
| 776 |
util.printD(output)
|
| 777 |
+
yield output
|
| 778 |
+
return
|
| 779 |
+
|
| 780 |
# get version info
|
| 781 |
ver_info = get_ver_info_by_ver_str(version_str, model_info)
|
| 782 |
if not ver_info:
|
| 783 |
+
output = "Failed to get version info, check console log for detail"
|
| 784 |
util.printD(output)
|
| 785 |
+
yield output
|
| 786 |
+
return
|
|
|
|
|
|
|
| 787 |
|
| 788 |
+
headers = {
|
| 789 |
+
"content-type": "application/json"
|
| 790 |
+
}
|
| 791 |
+
api_key = util.get_opts("ch_civiai_api_key")
|
| 792 |
+
if api_key:
|
| 793 |
+
headers["Authorization"] = f"Bearer {api_key}"
|
| 794 |
+
|
| 795 |
+
additional = None
|
| 796 |
+
for result in download_files(filename, folder, ver_info, headers, filetypes, dl_all, duplicate):
|
| 797 |
+
if not isinstance(result, str):
|
| 798 |
+
if len(result) > 2:
|
| 799 |
+
success, output, additional = result
|
| 800 |
+
else:
|
| 801 |
+
success, output = result
|
| 802 |
|
| 803 |
+
break
|
|
|
|
|
|
|
| 804 |
|
| 805 |
+
yield result
|
| 806 |
|
| 807 |
+
if not success:
|
| 808 |
+
yield output
|
| 809 |
+
return
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 810 |
|
|
|
|
|
|
|
|
|
|
| 811 |
# get version info
|
| 812 |
+
version_info = civitai.get_version_info_by_version_id(ver_info["id"])
|
| 813 |
+
model.process_model_info(output, version_info, model_type)
|
| 814 |
+
|
| 815 |
+
# then, get preview image + webui-visible progress
|
| 816 |
+
for result in civitai.get_preview_image_by_model_path(
|
| 817 |
+
output,
|
| 818 |
+
max_size_preview,
|
| 819 |
+
nsfw_preview_threshold,
|
| 820 |
+
preferred_preview=preview
|
| 821 |
+
):
|
| 822 |
+
yield f"Downloading model preview:\n{result}"
|
| 823 |
+
|
| 824 |
+
output = f"Done. Downloaded to: {output}"
|
| 825 |
+
if additional:
|
| 826 |
+
output = f"{output}. Additionally, the following failures occurred: \n{additional}"
|
| 827 |
util.printD(output)
|
| 828 |
+
yield output
|
extensions/Stable-Diffusion-Webui-Civitai-Helper/scripts/ch_lib/msg_handler.py
CHANGED
|
@@ -1,64 +1,74 @@
|
|
| 1 |
-
|
| 2 |
-
|
|
|
|
| 3 |
import json
|
| 4 |
from . import util
|
| 5 |
|
| 6 |
# action list
|
| 7 |
-
|
| 8 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 9 |
|
| 10 |
|
| 11 |
-
# handle request from javascript
|
| 12 |
-
# parameter: msg - msg from js as string in a hidden textbox
|
| 13 |
-
# return: dict for result
|
| 14 |
def parse_js_msg(msg):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 15 |
util.printD("Start parse js msg")
|
| 16 |
msg_dict = json.loads(msg)
|
| 17 |
|
| 18 |
# in case client side run JSON.stringify twice
|
| 19 |
-
if (
|
| 20 |
msg_dict = json.loads(msg_dict)
|
| 21 |
|
| 22 |
-
|
| 23 |
-
util.printD("Can not find action from js request")
|
| 24 |
-
return
|
| 25 |
-
|
| 26 |
-
action = msg_dict["action"]
|
| 27 |
if not action:
|
| 28 |
-
util.printD("
|
| 29 |
-
return
|
| 30 |
|
| 31 |
-
if action not in
|
| 32 |
-
util.printD("
|
| 33 |
-
return
|
| 34 |
|
| 35 |
util.printD("End parse js msg")
|
| 36 |
|
| 37 |
return msg_dict
|
| 38 |
|
| 39 |
|
| 40 |
-
# build python side msg for sending to js
|
| 41 |
-
# parameter: content dict
|
| 42 |
-
# return: msg as string, to fill into a hidden textbox
|
| 43 |
def build_py_msg(action:str, content:dict):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 44 |
util.printD("Start build_msg")
|
| 45 |
-
if not content:
|
| 46 |
-
util.
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
util.printD("Unknow action: " + action)
|
| 55 |
-
return
|
| 56 |
|
| 57 |
msg = {
|
| 58 |
"action" : action,
|
| 59 |
"content": content
|
| 60 |
}
|
| 61 |
|
| 62 |
-
|
| 63 |
util.printD("End build_msg")
|
| 64 |
-
return json.dumps(msg)
|
|
|
|
| 1 |
+
""" -*- coding: UTF-8 -*-
|
| 2 |
+
handle msg between js and python side
|
| 3 |
+
"""
|
| 4 |
import json
|
| 5 |
from . import util
|
| 6 |
|
| 7 |
# action list
|
| 8 |
+
JS_ACTIONS = (
|
| 9 |
+
"open_url",
|
| 10 |
+
"add_trigger_words",
|
| 11 |
+
"use_preview_prompt",
|
| 12 |
+
"dl_model_new_version",
|
| 13 |
+
"rename_card",
|
| 14 |
+
"remove_card"
|
| 15 |
+
)
|
| 16 |
+
|
| 17 |
+
PY_ACTIONS = (
|
| 18 |
+
"open_url",
|
| 19 |
+
"rename_card",
|
| 20 |
+
"remove_card"
|
| 21 |
+
)
|
| 22 |
|
| 23 |
|
|
|
|
|
|
|
|
|
|
| 24 |
def parse_js_msg(msg):
|
| 25 |
+
"""
|
| 26 |
+
handle request from javascript
|
| 27 |
+
parameter: msg - msg from js as string in a hidden textbox
|
| 28 |
+
return: dict for result
|
| 29 |
+
"""
|
| 30 |
util.printD("Start parse js msg")
|
| 31 |
msg_dict = json.loads(msg)
|
| 32 |
|
| 33 |
# in case client side run JSON.stringify twice
|
| 34 |
+
if isinstance(msg_dict, str):
|
| 35 |
msg_dict = json.loads(msg_dict)
|
| 36 |
|
| 37 |
+
action = msg_dict.get("action", "")
|
|
|
|
|
|
|
|
|
|
|
|
|
| 38 |
if not action:
|
| 39 |
+
util.printD("No action from js request")
|
| 40 |
+
return None
|
| 41 |
|
| 42 |
+
if action not in JS_ACTIONS:
|
| 43 |
+
util.printD(f"Unknown action: {action}")
|
| 44 |
+
return None
|
| 45 |
|
| 46 |
util.printD("End parse js msg")
|
| 47 |
|
| 48 |
return msg_dict
|
| 49 |
|
| 50 |
|
|
|
|
|
|
|
|
|
|
| 51 |
def build_py_msg(action:str, content:dict):
|
| 52 |
+
"""
|
| 53 |
+
build python side msg for sending to js
|
| 54 |
+
parameter: content dict
|
| 55 |
+
return: msg as string, to fill into a hidden textbox
|
| 56 |
+
"""
|
| 57 |
util.printD("Start build_msg")
|
| 58 |
+
if not (content and action and action in PY_ACTIONS):
|
| 59 |
+
util.indented_msg(
|
| 60 |
+
f"""
|
| 61 |
+
Could not run action on content:
|
| 62 |
+
{action=}
|
| 63 |
+
{content=}
|
| 64 |
+
"""
|
| 65 |
+
)
|
| 66 |
+
return None
|
|
|
|
|
|
|
| 67 |
|
| 68 |
msg = {
|
| 69 |
"action" : action,
|
| 70 |
"content": content
|
| 71 |
}
|
| 72 |
|
|
|
|
| 73 |
util.printD("End build_msg")
|
| 74 |
+
return json.dumps(msg)
|
extensions/Stable-Diffusion-Webui-Civitai-Helper/scripts/ch_lib/setting.py
DELETED
|
@@ -1,103 +0,0 @@
|
|
| 1 |
-
# -*- coding: UTF-8 -*-
|
| 2 |
-
# collecting settings to here
|
| 3 |
-
import json
|
| 4 |
-
import os
|
| 5 |
-
import modules.scripts as scripts
|
| 6 |
-
from . import util
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
name = "setting.json"
|
| 10 |
-
path = os.path.join(scripts.basedir(), name)
|
| 11 |
-
|
| 12 |
-
data = {
|
| 13 |
-
"model":{
|
| 14 |
-
"max_size_preview": True,
|
| 15 |
-
"skip_nsfw_preview": False
|
| 16 |
-
},
|
| 17 |
-
"general":{
|
| 18 |
-
"open_url_with_js": True,
|
| 19 |
-
"proxy": "",
|
| 20 |
-
},
|
| 21 |
-
"tool":{
|
| 22 |
-
}
|
| 23 |
-
}
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
# save setting
|
| 28 |
-
# return output msg for log
|
| 29 |
-
def save():
|
| 30 |
-
print("Saving setting to: " + path)
|
| 31 |
-
|
| 32 |
-
json_data = json.dumps(data, indent=4)
|
| 33 |
-
|
| 34 |
-
output = ""
|
| 35 |
-
|
| 36 |
-
#write to file
|
| 37 |
-
try:
|
| 38 |
-
with open(path, 'w') as f:
|
| 39 |
-
f.write(json_data)
|
| 40 |
-
except Exception as e:
|
| 41 |
-
util.printD("Error when writing file:"+path)
|
| 42 |
-
output = str(e)
|
| 43 |
-
util.printD(str(e))
|
| 44 |
-
return output
|
| 45 |
-
|
| 46 |
-
output = "Setting saved to: " + path
|
| 47 |
-
util.printD(output)
|
| 48 |
-
|
| 49 |
-
return output
|
| 50 |
-
|
| 51 |
-
|
| 52 |
-
# load setting to global data
|
| 53 |
-
def load():
|
| 54 |
-
# load data into globel data
|
| 55 |
-
global data
|
| 56 |
-
|
| 57 |
-
util.printD("Load setting from: " + path)
|
| 58 |
-
|
| 59 |
-
if not os.path.isfile(path):
|
| 60 |
-
util.printD("No setting file, use default")
|
| 61 |
-
return
|
| 62 |
-
|
| 63 |
-
json_data = None
|
| 64 |
-
with open(path, 'r') as f:
|
| 65 |
-
json_data = json.load(f)
|
| 66 |
-
|
| 67 |
-
# check error
|
| 68 |
-
if not json_data:
|
| 69 |
-
util.printD("load setting file failed")
|
| 70 |
-
return
|
| 71 |
-
|
| 72 |
-
data = json_data
|
| 73 |
-
|
| 74 |
-
# check for new key
|
| 75 |
-
if "proxy" not in data["general"].keys():
|
| 76 |
-
data["general"]["proxy"] = ""
|
| 77 |
-
|
| 78 |
-
|
| 79 |
-
return
|
| 80 |
-
|
| 81 |
-
# save setting from parameter
|
| 82 |
-
def save_from_input(max_size_preview, skip_nsfw_preview, open_url_with_js, proxy):
|
| 83 |
-
global data
|
| 84 |
-
data = {
|
| 85 |
-
"model":{
|
| 86 |
-
"max_size_preview": max_size_preview,
|
| 87 |
-
"skip_nsfw_preview": skip_nsfw_preview
|
| 88 |
-
},
|
| 89 |
-
"general":{
|
| 90 |
-
"open_url_with_js": open_url_with_js,
|
| 91 |
-
"proxy": proxy,
|
| 92 |
-
},
|
| 93 |
-
"tool":{
|
| 94 |
-
}
|
| 95 |
-
}
|
| 96 |
-
|
| 97 |
-
output = save()
|
| 98 |
-
|
| 99 |
-
if not output:
|
| 100 |
-
output = ""
|
| 101 |
-
|
| 102 |
-
return output
|
| 103 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
extensions/Stable-Diffusion-Webui-Civitai-Helper/scripts/ch_lib/templates.py
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
HTML templates
|
| 3 |
+
"""
|
| 4 |
+
|
| 5 |
+
from string import Template
|
| 6 |
+
from . import util
|
| 7 |
+
|
| 8 |
+
article = Template(util.dedent("""
|
| 9 |
+
<article style="margin: 5px; clear: both;">
|
| 10 |
+
$thumbnail
|
| 11 |
+
<div style="font-size:16px">
|
| 12 |
+
File: $model_path
|
| 13 |
+
</div>
|
| 14 |
+
$download
|
| 15 |
+
<div style="font-size:20px;margin:6px 0px;">
|
| 16 |
+
<b>Model: <a href="$url" target="_blank"><u>$model_name</u></a></b>
|
| 17 |
+
</div>
|
| 18 |
+
</article>
|
| 19 |
+
""").strip())
|
| 20 |
+
|
| 21 |
+
thumbnail = Template(util.dedent("""
|
| 22 |
+
<img src='$img_url' style='float: left; margin: 5px;'>
|
| 23 |
+
""").strip())
|
| 24 |
+
|
| 25 |
+
description = Template(util.dedent("""
|
| 26 |
+
<blockquote style="font-size:16px;margin:6px 0px;">
|
| 27 |
+
$description
|
| 28 |
+
</blockquote>
|
| 29 |
+
<br>
|
| 30 |
+
""").strip())
|
| 31 |
+
|
| 32 |
+
# add js function to download new version into SD webui by python
|
| 33 |
+
# in embed HTML, onclick= will also follow a ", never a ',
|
| 34 |
+
# so have to write it as following
|
| 35 |
+
download = Template(util.dedent("""
|
| 36 |
+
<div style="font-size:16px;margin:6px 0px;">
|
| 37 |
+
New Version: <u><a href="$download_url" target="_blank" style="margin:0px 10px;">$new_version_name</a></u>
|
| 38 |
+
<u><a href='#' style='margin:0px 10px;' onclick="ch_dl_model_new_version(event, '$model_path', '$new_version_id', '$download_url', '$model_type')">[Download into SD]</a></u>
|
| 39 |
+
</div>
|
| 40 |
+
""").strip())
|
| 41 |
+
|
| 42 |
+
no_download = Template(util.dedent("""
|
| 43 |
+
<div style="font-size:16px;margin:6px 0px;">
|
| 44 |
+
New Version: $new_version_name
|
| 45 |
+
</div>
|
| 46 |
+
""").strip())
|
| 47 |
+
|
| 48 |
+
duplicate_card = Template(util.dedent("""
|
| 49 |
+
<a href='#' onclick="remove_dup_card(event, '$model_type', '$search_term')" onmouseover="display_ch_path(event, '$path')" onmouseout="hide_ch_path(event)" onmousemove="move_ch_path(event)">
|
| 50 |
+
<div class='card' style=$style data-name="$name">
|
| 51 |
+
$background_image
|
| 52 |
+
<div class='actions'>
|
| 53 |
+
<div class='additional'>
|
| 54 |
+
<span style="display:none">$search_term</span>
|
| 55 |
+
</div>
|
| 56 |
+
<span class='name'>$name</span>
|
| 57 |
+
<span class='description'>$description</span>
|
| 58 |
+
</div>
|
| 59 |
+
</div>
|
| 60 |
+
</a>
|
| 61 |
+
""").strip())
|
| 62 |
+
|
| 63 |
+
duplicate_preview = Template(util.dedent("""
|
| 64 |
+
<img src="./sd_extra_networks/thumb?filename=$bg_image" class="preview" loading="lazy">
|
| 65 |
+
""").strip())
|
| 66 |
+
|
| 67 |
+
duplicate_article = Template(util.dedent("""
|
| 68 |
+
<article>
|
| 69 |
+
<h1>
|
| 70 |
+
$section_name
|
| 71 |
+
</h1>
|
| 72 |
+
$contents
|
| 73 |
+
</article>
|
| 74 |
+
""").strip())
|
| 75 |
+
|
| 76 |
+
duplicate_row = Template(util.dedent("""
|
| 77 |
+
<div id="ch_$hash">
|
| 78 |
+
<div class=civitai_name>
|
| 79 |
+
<h2>
|
| 80 |
+
$civitai_name
|
| 81 |
+
</h2>
|
| 82 |
+
</div>
|
| 83 |
+
<div class=duplicate_model id="ch_${hash}_cards">
|
| 84 |
+
$columns
|
| 85 |
+
</div>
|
| 86 |
+
</div>
|
| 87 |
+
""").strip())
|
| 88 |
+
|
| 89 |
+
|
| 90 |
+
duplicate_column = Template(util.dedent("""
|
| 91 |
+
<div class=dup_$count>
|
| 92 |
+
$card
|
| 93 |
+
</div>
|
| 94 |
+
""").strip())
|
extensions/Stable-Diffusion-Webui-Civitai-Helper/scripts/ch_lib/util.py
CHANGED
|
@@ -1,108 +1,493 @@
|
|
| 1 |
-
|
|
|
|
|
|
|
|
|
|
| 2 |
import os
|
| 3 |
import io
|
|
|
|
| 4 |
import hashlib
|
| 5 |
-
import
|
| 6 |
-
import
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 7 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 8 |
|
| 9 |
-
|
|
|
|
| 10 |
|
| 11 |
-
|
| 12 |
-
|
| 13 |
|
|
|
|
|
|
|
| 14 |
|
| 15 |
-
|
|
|
|
| 16 |
|
| 17 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 18 |
|
| 19 |
|
| 20 |
# print for debugging
|
| 21 |
-
def printD(msg):
|
|
|
|
| 22 |
print(f"Civitai Helper: {msg}")
|
| 23 |
|
| 24 |
|
| 25 |
-
def
|
| 26 |
-
"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 27 |
while True:
|
| 28 |
chunk = file.read(size)
|
| 29 |
if not chunk:
|
| 30 |
break
|
| 31 |
yield chunk
|
| 32 |
|
| 33 |
-
# Now, hashing use the same way as pip's source code.
|
| 34 |
-
def gen_file_sha256(filname):
|
| 35 |
-
printD("Use Memory Optimized SHA256")
|
| 36 |
-
blocksize=1 << 20
|
| 37 |
-
h = hashlib.sha256()
|
| 38 |
-
length = 0
|
| 39 |
-
with open(os.path.realpath(filname), 'rb') as f:
|
| 40 |
-
for block in read_chunks(f, size=blocksize):
|
| 41 |
-
length += len(block)
|
| 42 |
-
h.update(block)
|
| 43 |
-
|
| 44 |
-
hash_value = h.hexdigest()
|
| 45 |
-
printD("sha256: " + hash_value)
|
| 46 |
-
printD("length: " + str(length))
|
| 47 |
-
return hash_value
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
|
| 51 |
-
# get preview image
|
| 52 |
-
def download_file(url, path):
|
| 53 |
-
printD("Downloading file from: " + url)
|
| 54 |
-
# get file
|
| 55 |
-
r = requests.get(url, stream=True, headers=def_headers, proxies=proxies)
|
| 56 |
-
if not r.ok:
|
| 57 |
-
printD("Get error code: " + str(r.status_code))
|
| 58 |
-
printD(r.text)
|
| 59 |
-
return
|
| 60 |
-
|
| 61 |
-
# write to file
|
| 62 |
-
with open(os.path.realpath(path), 'wb') as f:
|
| 63 |
-
r.raw.decode_content = True
|
| 64 |
-
shutil.copyfileobj(r.raw, f)
|
| 65 |
-
|
| 66 |
-
printD("File downloaded to: " + path)
|
| 67 |
|
| 68 |
-
|
| 69 |
-
|
| 70 |
-
printD("Get subfolder for:
|
| 71 |
if not folder:
|
| 72 |
printD("folder can not be None")
|
| 73 |
-
return
|
| 74 |
-
|
| 75 |
if not os.path.isdir(folder):
|
| 76 |
printD("path is not a folder")
|
| 77 |
-
return
|
| 78 |
-
|
| 79 |
prefix_len = len(folder)
|
|
|
|
| 80 |
subfolders = []
|
| 81 |
-
for root, dirs,
|
| 82 |
-
|
| 83 |
-
|
| 84 |
-
|
| 85 |
-
|
| 86 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 87 |
|
| 88 |
return subfolders
|
| 89 |
|
| 90 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 91 |
# get relative path
|
| 92 |
def get_relative_path(item_path:str, parent_path:str) -> str:
|
| 93 |
-
|
| 94 |
-
|
| 95 |
-
|
| 96 |
-
|
| 97 |
-
|
| 98 |
-
|
|
|
|
| 99 |
return ""
|
|
|
|
| 100 |
if not item_path.startswith(parent_path):
|
|
|
|
| 101 |
return item_path
|
| 102 |
|
| 103 |
relative = item_path[len(parent_path):]
|
| 104 |
if relative[:1] == "/" or relative[:1] == "\\":
|
| 105 |
relative = relative[1:]
|
| 106 |
|
| 107 |
-
# printD("relative:
|
| 108 |
-
return relative
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
""" -*- coding: UTF-8 -*-
|
| 2 |
+
Utility functions for Stable Diffusion Civitai Helper
|
| 3 |
+
"""
|
| 4 |
+
from __future__ import annotations
|
| 5 |
import os
|
| 6 |
import io
|
| 7 |
+
import re
|
| 8 |
import hashlib
|
| 9 |
+
import textwrap
|
| 10 |
+
import time
|
| 11 |
+
import subprocess
|
| 12 |
+
import gradio as gr
|
| 13 |
+
from modules import shared
|
| 14 |
+
from modules.shared import opts
|
| 15 |
+
from modules import hashes
|
| 16 |
+
import launch
|
| 17 |
+
from packaging.version import parse as parse_version
|
| 18 |
|
| 19 |
+
try:
|
| 20 |
+
# Automatic1111 SD WebUI
|
| 21 |
+
import modules.cache as sha256_cache
|
| 22 |
+
except ModuleNotFoundError:
|
| 23 |
+
# Vladmandic "SD.Next"
|
| 24 |
+
import modules.hashes as sha256_cache
|
| 25 |
|
| 26 |
+
# used to append extension information to JSON/INFO files
|
| 27 |
+
SHORT_NAME = "sd_civitai_helper"
|
| 28 |
|
| 29 |
+
# current version of the exension
|
| 30 |
+
VERSION = "1.8.1"
|
| 31 |
|
| 32 |
+
# Civitai INFO files below this version will regenerated
|
| 33 |
+
COMPAT_VERSION_CIVITAI = "1.7.2"
|
| 34 |
|
| 35 |
+
# SD webui model info JSON below this version will be regenerated
|
| 36 |
+
COMPAT_VERSION_SDWEBUI = "1.8.0"
|
| 37 |
|
| 38 |
+
DEFAULT_HEADERS = {
|
| 39 |
+
"User-Agent": (
|
| 40 |
+
"Mozilla/5.0 (iPad; CPU OS 12_2 like Mac OS X) "
|
| 41 |
+
"AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148"
|
| 42 |
+
)
|
| 43 |
+
}
|
| 44 |
+
|
| 45 |
+
PROXIES = {
|
| 46 |
+
"http": None,
|
| 47 |
+
"https": None,
|
| 48 |
+
}
|
| 49 |
+
|
| 50 |
+
REQUEST_TIMEOUT = 300 # 5 minutes
|
| 51 |
+
REQUEST_RETRIES = 5
|
| 52 |
+
|
| 53 |
+
_MINUTE = 60
|
| 54 |
+
_HOUR = _MINUTE * 60
|
| 55 |
+
_DAY = _HOUR * 24
|
| 56 |
|
| 57 |
|
| 58 |
# print for debugging
|
| 59 |
+
def printD(msg:any) -> str:
|
| 60 |
+
""" Print a message to stderr """
|
| 61 |
print(f"Civitai Helper: {msg}")
|
| 62 |
|
| 63 |
|
| 64 |
+
def append_default_headers(headers:dict) -> dict:
|
| 65 |
+
""" Append extension default values to customized headers where missing """
|
| 66 |
+
|
| 67 |
+
for key, val in DEFAULT_HEADERS.items():
|
| 68 |
+
if key not in headers:
|
| 69 |
+
headers[key] = val
|
| 70 |
+
return headers
|
| 71 |
+
|
| 72 |
+
|
| 73 |
+
def indented_msg(msg:str) -> str:
|
| 74 |
+
"""
|
| 75 |
+
Clean up an indented message in the format of
|
| 76 |
+
[header]
|
| 77 |
+
var1=var1
|
| 78 |
+
var2=var2
|
| 79 |
+
var3=var3
|
| 80 |
+
|
| 81 |
+
and print the results in the format of:
|
| 82 |
+
|
| 83 |
+
Civitai Helper: [header]
|
| 84 |
+
var1: var1
|
| 85 |
+
var2: var2
|
| 86 |
+
var3: var3
|
| 87 |
+
|
| 88 |
+
return: msg:str
|
| 89 |
+
"""
|
| 90 |
+
|
| 91 |
+
msg_parts = textwrap.dedent(
|
| 92 |
+
msg
|
| 93 |
+
).strip().split('\n')
|
| 94 |
+
msg = [msg_parts.pop(0)]
|
| 95 |
+
for part in msg_parts:
|
| 96 |
+
part = ": ".join(part.split("="))
|
| 97 |
+
msg.append(f" {part}")
|
| 98 |
+
msg = "\n".join(msg)
|
| 99 |
+
|
| 100 |
+
return msg
|
| 101 |
+
|
| 102 |
+
|
| 103 |
+
def delay(seconds:float) -> None:
|
| 104 |
+
""" delay before next request, mostly to prevent being treated as a DDoS """
|
| 105 |
+
time.sleep(seconds)
|
| 106 |
+
|
| 107 |
+
|
| 108 |
+
def is_stale(timestamp:float) -> bool:
|
| 109 |
+
""" Returns if a timestamp was more than a day ago. """
|
| 110 |
+
cur_time = ch_time()
|
| 111 |
+
elapsed = cur_time - timestamp
|
| 112 |
+
|
| 113 |
+
if elapsed > _DAY:
|
| 114 |
+
return True
|
| 115 |
+
|
| 116 |
+
return False
|
| 117 |
+
|
| 118 |
+
|
| 119 |
+
def info(msg:str) -> None:
|
| 120 |
+
""" Display an info smessage on the client DOM """
|
| 121 |
+
gr.Info(msg)
|
| 122 |
+
|
| 123 |
+
|
| 124 |
+
def warning(msg:str) -> None:
|
| 125 |
+
""" Display a warning message on the client DOM """
|
| 126 |
+
gr.Warning(msg)
|
| 127 |
+
|
| 128 |
+
|
| 129 |
+
def error(msg:str) -> None:
|
| 130 |
+
""" Display an error message on the client DOM """
|
| 131 |
+
gr.Error(msg)
|
| 132 |
+
|
| 133 |
+
|
| 134 |
+
def ch_time() -> int:
|
| 135 |
+
""" Unix timestamp """
|
| 136 |
+
return int(time.time())
|
| 137 |
+
|
| 138 |
+
|
| 139 |
+
def dedent(text:str) -> str:
|
| 140 |
+
""" alias for textwrap.dedent """
|
| 141 |
+
return textwrap.dedent(text)
|
| 142 |
+
|
| 143 |
+
|
| 144 |
+
def get_name(model_path:str, model_type:str) -> str:
|
| 145 |
+
""" return: {model_type}/{model_name} """
|
| 146 |
+
|
| 147 |
+
model_name = os.path.splitext(os.path.basename(model_path))[0]
|
| 148 |
+
return f"{model_type}/{model_name}"
|
| 149 |
+
|
| 150 |
+
|
| 151 |
+
def get_opts(key):
|
| 152 |
+
""" return: option value """
|
| 153 |
+
return opts.data.get(key, None)
|
| 154 |
+
|
| 155 |
+
|
| 156 |
+
def gen_file_sha256(filename:str, model_type="lora", use_addnet_hash=False) -> str:
|
| 157 |
+
""" return a sha256 hash for a file """
|
| 158 |
+
|
| 159 |
+
cache = sha256_cache.cache
|
| 160 |
+
dump_cache = sha256_cache.dump_cache
|
| 161 |
+
model_name = get_name(filename, model_type)
|
| 162 |
+
|
| 163 |
+
sha256_hashes = None
|
| 164 |
+
if use_addnet_hash:
|
| 165 |
+
sha256_hashes = cache("hashes-addnet")
|
| 166 |
+
else:
|
| 167 |
+
sha256_hashes = cache("hashes")
|
| 168 |
+
|
| 169 |
+
sha256_value = hashes.sha256_from_cache(filename, model_name, use_addnet_hash)
|
| 170 |
+
if sha256_value is not None:
|
| 171 |
+
yield sha256_value
|
| 172 |
+
return
|
| 173 |
+
|
| 174 |
+
if shared.cmd_opts.no_hashing:
|
| 175 |
+
printD("SD WebUI Civitai Helper requires hashing functions for this feature. \
|
| 176 |
+
Please remove the commandline argument `--no-hashing` for this functionality.")
|
| 177 |
+
yield None
|
| 178 |
+
return
|
| 179 |
+
|
| 180 |
+
with open(filename, "rb") as model_file:
|
| 181 |
+
result = None
|
| 182 |
+
for result in calculate_sha256(model_file, use_addnet_hash):
|
| 183 |
+
yield result
|
| 184 |
+
sha256_value = result
|
| 185 |
+
|
| 186 |
+
printD(f"sha256: {sha256_value}")
|
| 187 |
+
|
| 188 |
+
sha256_hashes[model_name] = {
|
| 189 |
+
"mtime": os.path.getmtime(filename),
|
| 190 |
+
"sha256": sha256_value,
|
| 191 |
+
}
|
| 192 |
+
|
| 193 |
+
dump_cache()
|
| 194 |
+
|
| 195 |
+
yield sha256_value
|
| 196 |
+
|
| 197 |
+
def calculate_sha256(model_file, use_addnet_hash=False):
|
| 198 |
+
""" calculate the sha256 hash for a model file """
|
| 199 |
+
|
| 200 |
+
blocksize= 1 << 20
|
| 201 |
+
sha256_hash = hashlib.sha256()
|
| 202 |
+
|
| 203 |
+
size = os.fstat(model_file.fileno()).st_size
|
| 204 |
+
|
| 205 |
+
offset = 0
|
| 206 |
+
if use_addnet_hash:
|
| 207 |
+
model_file.seek(0)
|
| 208 |
+
header= model_file.read(8)
|
| 209 |
+
offset = int.from_bytes(header, "little") + 8
|
| 210 |
+
model_file.seek(offset)
|
| 211 |
+
|
| 212 |
+
pos = 0
|
| 213 |
+
for block in read_chunks(model_file, size=blocksize):
|
| 214 |
+
pos += len(block)
|
| 215 |
+
|
| 216 |
+
percent = (pos - offset) / (size - offset)
|
| 217 |
+
|
| 218 |
+
yield (percent, f"hashing model {model_file.name}")
|
| 219 |
+
|
| 220 |
+
sha256_hash.update(block)
|
| 221 |
+
|
| 222 |
+
hash_value = sha256_hash.hexdigest()
|
| 223 |
+
yield hash_value
|
| 224 |
+
|
| 225 |
+
|
| 226 |
+
def read_chunks(file, size=io.DEFAULT_BUFFER_SIZE) -> bytes:
|
| 227 |
+
""" Yield pieces of data from a file-like object until EOF. """
|
| 228 |
while True:
|
| 229 |
chunk = file.read(size)
|
| 230 |
if not chunk:
|
| 231 |
break
|
| 232 |
yield chunk
|
| 233 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 234 |
|
| 235 |
+
def get_subfolders(folder:str) -> list[str]:
|
| 236 |
+
""" return: list of subfolders """
|
| 237 |
+
printD(f"Get subfolder for: {folder}")
|
| 238 |
if not folder:
|
| 239 |
printD("folder can not be None")
|
| 240 |
+
return []
|
| 241 |
+
|
| 242 |
if not os.path.isdir(folder):
|
| 243 |
printD("path is not a folder")
|
| 244 |
+
return []
|
| 245 |
+
|
| 246 |
prefix_len = len(folder)
|
| 247 |
+
full_dirs_searched = []
|
| 248 |
subfolders = []
|
| 249 |
+
for root, dirs, _ in os.walk(folder, followlinks=True):
|
| 250 |
+
if root == folder:
|
| 251 |
+
continue
|
| 252 |
+
|
| 253 |
+
# Prevent following recursive symlinks
|
| 254 |
+
follow = []
|
| 255 |
+
for directory in dirs:
|
| 256 |
+
full_dir_path = os.path.join(root, directory)
|
| 257 |
+
try:
|
| 258 |
+
canonical_dir = os.path.realpath(full_dir_path, strict=True)
|
| 259 |
+
if canonical_dir not in full_dirs_searched:
|
| 260 |
+
full_dirs_searched.append(canonical_dir)
|
| 261 |
+
follow.append(directory)
|
| 262 |
+
|
| 263 |
+
except OSError:
|
| 264 |
+
printD(f"Symlink loop: {directory}")
|
| 265 |
+
continue
|
| 266 |
+
|
| 267 |
+
# Get subfolder path
|
| 268 |
+
subfolder = root[prefix_len:]
|
| 269 |
+
subfolders.append(subfolder)
|
| 270 |
+
|
| 271 |
+
# Update dirs parameter to prevent following recursive symlinks
|
| 272 |
+
dirs[:] = follow
|
| 273 |
|
| 274 |
return subfolders
|
| 275 |
|
| 276 |
|
| 277 |
+
def find_file_in_folders(folders:list, filename:str) -> str:
|
| 278 |
+
"""
|
| 279 |
+
Searches a directory for a filename,
|
| 280 |
+
|
| 281 |
+
return: filename:str or None
|
| 282 |
+
"""
|
| 283 |
+
for folder in folders:
|
| 284 |
+
for root, _, files in os.walk(folder, followlinks=True):
|
| 285 |
+
if filename in files:
|
| 286 |
+
# found file
|
| 287 |
+
return os.path.join(root, filename)
|
| 288 |
+
|
| 289 |
+
return None
|
| 290 |
+
|
| 291 |
+
|
| 292 |
# get relative path
|
| 293 |
def get_relative_path(item_path:str, parent_path:str) -> str:
|
| 294 |
+
"""
|
| 295 |
+
Gets a relative path from an absolute path and its parent_path
|
| 296 |
+
item path must start with parent_path
|
| 297 |
+
return: relative_path:str
|
| 298 |
+
"""
|
| 299 |
+
|
| 300 |
+
if not (item_path and parent_path):
|
| 301 |
return ""
|
| 302 |
+
|
| 303 |
if not item_path.startswith(parent_path):
|
| 304 |
+
# return absolute path
|
| 305 |
return item_path
|
| 306 |
|
| 307 |
relative = item_path[len(parent_path):]
|
| 308 |
if relative[:1] == "/" or relative[:1] == "\\":
|
| 309 |
relative = relative[1:]
|
| 310 |
|
| 311 |
+
# printD(f"relative: {relative}")
|
| 312 |
+
return relative
|
| 313 |
+
|
| 314 |
+
|
| 315 |
+
# Allowed HTML tags
|
| 316 |
+
whitelist = re.compile(r"</?(a|img|br|p|b|strong|i|h[0-9]|code)[^>]*>")
|
| 317 |
+
|
| 318 |
+
# Allowed HTML attributes
|
| 319 |
+
attrs = re.compile(r"""(?:href|src|target)=['"]?[^\s'"]*['"]?""")
|
| 320 |
+
|
| 321 |
+
def safe_html_replace(match) -> str:
|
| 322 |
+
""" Given a block of text, returns that block with most HTML removed
|
| 323 |
+
and unneeded attributes pruned.
|
| 324 |
+
"""
|
| 325 |
+
tag = None
|
| 326 |
+
attr = None
|
| 327 |
+
close = False
|
| 328 |
+
|
| 329 |
+
match = whitelist.match(match.group(0))
|
| 330 |
+
if match is not None:
|
| 331 |
+
html_elem = match.group(0)
|
| 332 |
+
tag = match.group(1)
|
| 333 |
+
close = html_elem[1] == "/"
|
| 334 |
+
if (tag in ["a", "img"]) and not close:
|
| 335 |
+
sub_match = attrs.findall(html_elem)
|
| 336 |
+
if sub_match is not None:
|
| 337 |
+
attr = " ".join(sub_match)
|
| 338 |
+
|
| 339 |
+
if close:
|
| 340 |
+
return f"</{tag}>"
|
| 341 |
+
|
| 342 |
+
return f"<{tag} {attr}>" if attr else f"<{tag}>"
|
| 343 |
+
|
| 344 |
+
return ""
|
| 345 |
+
|
| 346 |
+
def safe_html(html:str) -> str:
|
| 347 |
+
""" whitelist only HTML I"m comfortable displaying in webui """
|
| 348 |
+
|
| 349 |
+
return re.sub("<[^<]+?>", safe_html_replace, html)
|
| 350 |
+
|
| 351 |
+
|
| 352 |
+
def trim_html(html:str) -> str:
|
| 353 |
+
""" Remove any HTML for a given string and, if needed, replace it with
|
| 354 |
+
a comparable plain-text alternative.
|
| 355 |
+
"""
|
| 356 |
+
|
| 357 |
+
def sub_tag(match):
|
| 358 |
+
tag = match.group(1)
|
| 359 |
+
if tag == "/p":
|
| 360 |
+
return "\n\n"
|
| 361 |
+
if tag == "br":
|
| 362 |
+
return "\n"
|
| 363 |
+
if tag == "li":
|
| 364 |
+
return "* "
|
| 365 |
+
if tag in ["code", "/code"]:
|
| 366 |
+
return "`"
|
| 367 |
+
return ''
|
| 368 |
+
|
| 369 |
+
def sub_escaped(match):
|
| 370 |
+
escaped = match.group(1)
|
| 371 |
+
unescaped = {
|
| 372 |
+
"gt": ">",
|
| 373 |
+
"lt": "<",
|
| 374 |
+
"quot": '"',
|
| 375 |
+
"amp": "&"
|
| 376 |
+
}
|
| 377 |
+
return unescaped.get(escaped, "")
|
| 378 |
+
|
| 379 |
+
# non-breaking space. Useless unstyled content
|
| 380 |
+
html = html.replace("\u00a0", "")
|
| 381 |
+
|
| 382 |
+
# remove non-whitelisted HTML tags,
|
| 383 |
+
# replace whitelisted tags with text-equivalents
|
| 384 |
+
html = re.sub(r"<(/?[a-zA-Z]+)(?:[^>]+)?>", sub_tag, html)
|
| 385 |
+
|
| 386 |
+
# Replace HTML-escaped characters with displayables.
|
| 387 |
+
html = re.sub(r"\&(gt|lt|quot|amp)\;", sub_escaped, html)
|
| 388 |
+
|
| 389 |
+
# Because we encapsulate the description in HTML comment,
|
| 390 |
+
# We have to prevent those comments from being cancelled.
|
| 391 |
+
html.replace("-->", "→")
|
| 392 |
+
|
| 393 |
+
# https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/13241
|
| 394 |
+
return f"<!--\n{html.strip()}\n-->"
|
| 395 |
+
|
| 396 |
+
|
| 397 |
+
def newer_version(ver1:str, ver2:str, allow_equal=False) -> bool:
|
| 398 |
+
""" Returns ver1 > ver2
|
| 399 |
+
if allow_equal, returns ver1 >= ver2
|
| 400 |
+
"""
|
| 401 |
+
if allow_equal:
|
| 402 |
+
return parse_version(ver1) >= parse_version(ver2)
|
| 403 |
+
|
| 404 |
+
return parse_version(ver1) > parse_version(ver2)
|
| 405 |
+
|
| 406 |
+
|
| 407 |
+
def metadata_version(metadata:dict) -> str | bool:
|
| 408 |
+
""" Attempts retrieve the extension version used to create
|
| 409 |
+
to create the object block
|
| 410 |
+
"""
|
| 411 |
+
try:
|
| 412 |
+
return metadata["extensions"][SHORT_NAME]["version"]
|
| 413 |
+
except KeyError:
|
| 414 |
+
return False
|
| 415 |
+
|
| 416 |
+
|
| 417 |
+
def create_extension_block(data=None, skeleton=False) -> dict:
|
| 418 |
+
""" Creates or edits an extensions block for usage in JSON files
|
| 419 |
+
created or edited by this extension.
|
| 420 |
+
|
| 421 |
+
Adds the current version of this extension to the extensions block
|
| 422 |
+
"""
|
| 423 |
+
|
| 424 |
+
cur_time = ch_time()
|
| 425 |
+
|
| 426 |
+
block = {
|
| 427 |
+
SHORT_NAME: {
|
| 428 |
+
"version": VERSION,
|
| 429 |
+
"last_update": cur_time,
|
| 430 |
+
"skeleton_file": skeleton
|
| 431 |
+
}
|
| 432 |
+
}
|
| 433 |
+
|
| 434 |
+
if not data:
|
| 435 |
+
return block
|
| 436 |
+
|
| 437 |
+
data[SHORT_NAME] = block[SHORT_NAME]
|
| 438 |
+
|
| 439 |
+
return data
|
| 440 |
+
|
| 441 |
+
|
| 442 |
+
def webui_version() -> str:
|
| 443 |
+
''' Gets the current webui version using webui's launch tools
|
| 444 |
+
|
| 445 |
+
The version is expected to be in the format `v1.6.0-128-g792589fd`,
|
| 446 |
+
tho all that is explicitly required is `vX`.
|
| 447 |
+
|
| 448 |
+
returns the version in the form 'X.Y.Z'
|
| 449 |
+
'''
|
| 450 |
+
version = None
|
| 451 |
+
try:
|
| 452 |
+
tag = launch.git_tag()
|
| 453 |
+
match = re.match(r"v([\d.]+)", tag)
|
| 454 |
+
if match:
|
| 455 |
+
version = match.group(1)
|
| 456 |
+
else:
|
| 457 |
+
# XXX assume a modern SD Webui version if one cannot be found.
|
| 458 |
+
version = "1.6.0"
|
| 459 |
+
|
| 460 |
+
except AttributeError:
|
| 461 |
+
try:
|
| 462 |
+
return subprocess.check_output(
|
| 463 |
+
["git", "describe", "--tags"],
|
| 464 |
+
shell=False,
|
| 465 |
+
encoding='utf8'
|
| 466 |
+
).strip()
|
| 467 |
+
|
| 468 |
+
except subprocess.SubprocessError:
|
| 469 |
+
try:
|
| 470 |
+
changelog_md = os.path.join(
|
| 471 |
+
os.path.dirname(os.path.dirname(__file__)),
|
| 472 |
+
"CHANGELOG.md"
|
| 473 |
+
)
|
| 474 |
+
with open(changelog_md, "r", encoding="utf-8") as file:
|
| 475 |
+
line = next((line.strip() for line in file if line.strip()), "<none>")
|
| 476 |
+
line = line.replace("## ", "")
|
| 477 |
+
version = line
|
| 478 |
+
|
| 479 |
+
except OSError:
|
| 480 |
+
version = "1.6.0"
|
| 481 |
+
|
| 482 |
+
return version
|
| 483 |
+
|
| 484 |
+
|
| 485 |
+
filename_re = re.compile(r"[^A-Za-z\d\s\^\-\+_.\(\)\[\]]")
|
| 486 |
+
def bash_filename(filename:str) -> str:
|
| 487 |
+
"""
|
| 488 |
+
Bashes a filename with a large fish until I'm comfortable using it.
|
| 489 |
+
|
| 490 |
+
Keeps a limited set of valid characters, but does not account for
|
| 491 |
+
reserved names like COM.
|
| 492 |
+
"""
|
| 493 |
+
return re.sub(filename_re, "", filename)
|
extensions/Stable-Diffusion-Webui-Civitai-Helper/scripts/civitai_helper.py
CHANGED
|
@@ -1,59 +1,68 @@
|
|
| 1 |
-
|
| 2 |
-
|
| 3 |
-
|
|
|
|
|
|
|
| 4 |
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
import modules.scripts as scripts
|
| 8 |
-
import gradio as gr
|
| 9 |
import os
|
| 10 |
-
import
|
| 11 |
-
import requests
|
| 12 |
-
import random
|
| 13 |
-
import hashlib
|
| 14 |
-
import json
|
| 15 |
-
import shutil
|
| 16 |
-
import re
|
| 17 |
import modules
|
| 18 |
-
from modules import
|
| 19 |
from modules import shared
|
|
|
|
| 20 |
from scripts.ch_lib import model
|
| 21 |
from scripts.ch_lib import js_action_civitai
|
| 22 |
-
from scripts.ch_lib import model_action_civitai
|
| 23 |
from scripts.ch_lib import civitai
|
| 24 |
from scripts.ch_lib import util
|
| 25 |
-
|
| 26 |
|
| 27 |
# init
|
| 28 |
-
|
| 29 |
# root path
|
| 30 |
-
|
| 31 |
|
| 32 |
# extension path
|
| 33 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 34 |
|
| 35 |
model.get_custom_model_folder()
|
| 36 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 37 |
|
| 38 |
-
# Setting now can not be saved from extension tab
|
| 39 |
-
# All settings now must be saved from setting page.
|
| 40 |
-
def on_ui_settings():
|
| 41 |
-
ch_section = ("civitai_helper", "Civitai Helper")
|
| 42 |
-
# settings
|
| 43 |
-
shared.opts.add_option("ch_max_size_preview", shared.OptionInfo(True, "Download Max Size Preview", gr.Checkbox, {"interactive": True}, section=ch_section))
|
| 44 |
-
shared.opts.add_option("ch_skip_nsfw_preview", shared.OptionInfo(False, "Skip NSFW Preview Images", gr.Checkbox, {"interactive": True}, section=ch_section))
|
| 45 |
-
shared.opts.add_option("ch_open_url_with_js", shared.OptionInfo(True, "Open Url At Client Side", gr.Checkbox, {"interactive": True}, section=ch_section))
|
| 46 |
-
shared.opts.add_option("ch_proxy", shared.OptionInfo("", "Civitai Helper Proxy", gr.Textbox, {"interactive": True, "lines":1, "info":"format: socks5h://127.0.0.1:port"}, section=ch_section))
|
| 47 |
-
shared.opts.add_option("ch_civiai_api_key", shared.OptionInfo("", "Civitai API Key", gr.Textbox, {"interactive": True, "lines":1, "info":"check doc:https://github.com/zixaphir/Stable-Diffusion-Webui-Civitai-Helper/tree/master#api-key"}, section=ch_section))
|
| 48 |
|
| 49 |
def on_ui_tabs():
|
| 50 |
# init
|
| 51 |
# init_py_msg = {
|
| 52 |
# # relative extension path
|
| 53 |
-
# "
|
| 54 |
# }
|
| 55 |
# init_py_msg_str = json.dumps(init_py_msg)
|
| 56 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 57 |
|
| 58 |
# get prompt textarea
|
| 59 |
# check modules/ui.py, search for txt2img_paste_fields
|
|
@@ -63,190 +72,245 @@ def on_ui_tabs():
|
|
| 63 |
img2img_prompt = modules.ui.img2img_paste_fields[0][0]
|
| 64 |
img2img_neg_prompt = modules.ui.img2img_paste_fields[1][0]
|
| 65 |
|
| 66 |
-
|
| 67 |
-
|
| 68 |
-
|
| 69 |
-
|
| 70 |
-
|
| 71 |
-
|
| 72 |
-
|
| 73 |
-
|
| 74 |
-
util.printD("Settings:")
|
| 75 |
-
util.printD("max_size_preview: " + str(max_size_preview))
|
| 76 |
-
util.printD("skip_nsfw_preview: " + str(skip_nsfw_preview))
|
| 77 |
-
util.printD("open_url_with_js: " + str(open_url_with_js))
|
| 78 |
-
util.printD("proxy: " + str(proxy))
|
| 79 |
-
|
| 80 |
-
# set civitai_api_key
|
| 81 |
-
has_api_key = False
|
| 82 |
-
if civitai_api_key:
|
| 83 |
-
has_api_key = True
|
| 84 |
-
util.civitai_api_key = civitai_api_key
|
| 85 |
-
util.def_headers["Authorization"] = f"Bearer {civitai_api_key}"
|
| 86 |
-
|
| 87 |
-
util.printD(f"use civitai api key: {has_api_key}")
|
| 88 |
-
|
| 89 |
-
# set proxy
|
| 90 |
-
if proxy:
|
| 91 |
-
util.proxies = {
|
| 92 |
-
"http": proxy,
|
| 93 |
-
"https": proxy,
|
| 94 |
-
}
|
| 95 |
-
|
| 96 |
-
|
| 97 |
-
# ====Event's function====
|
| 98 |
-
def scan_model(scan_model_types):
|
| 99 |
-
return model_action_civitai.scan_model(scan_model_types, max_size_preview, skip_nsfw_preview)
|
| 100 |
-
|
| 101 |
-
def get_model_info_by_input(model_type_drop, model_name_drop, model_url_or_id_txtbox):
|
| 102 |
-
return model_action_civitai.get_model_info_by_input(model_type_drop, model_name_drop, model_url_or_id_txtbox, max_size_preview, skip_nsfw_preview)
|
| 103 |
-
|
| 104 |
-
def dl_model_by_input(dl_model_info, dl_model_type_txtbox, dl_subfolder_drop, dl_version_drop, dl_all_ckb):
|
| 105 |
-
return model_action_civitai.dl_model_by_input(dl_model_info, dl_model_type_txtbox, dl_subfolder_drop, dl_version_drop, dl_all_ckb, max_size_preview, skip_nsfw_preview)
|
| 106 |
-
|
| 107 |
-
def check_models_new_version_to_md(dl_model_info, dl_model_type_txtbox, dl_subfolder_drop, dl_version_drop, dl_all_ckb):
|
| 108 |
-
return model_action_civitai.check_models_new_version_to_md(dl_model_info, dl_model_type_txtbox, dl_subfolder_drop, dl_version_drop, dl_all_ckb, max_size_preview, skip_nsfw_preview)
|
| 109 |
-
|
| 110 |
-
def open_model_url(js_msg_txtbox):
|
| 111 |
-
return js_action_civitai.open_model_url(js_msg_txtbox, open_url_with_js)
|
| 112 |
-
|
| 113 |
-
def dl_model_new_version(js_msg_txtbox, max_size_preview):
|
| 114 |
-
return js_action_civitai.dl_model_new_version(js_msg_txtbox, max_size_preview, skip_nsfw_preview)
|
| 115 |
-
|
| 116 |
-
|
| 117 |
-
def get_model_names_by_input(model_type, empty_info_only):
|
| 118 |
-
names = civitai.get_model_names_by_input(model_type, empty_info_only)
|
| 119 |
-
return model_name_drop.update(choices=names)
|
| 120 |
-
|
| 121 |
-
def get_model_info_by_url(url):
|
| 122 |
-
r = model_action_civitai.get_model_info_by_url(url)
|
| 123 |
-
|
| 124 |
-
model_info = {}
|
| 125 |
-
model_name = ""
|
| 126 |
-
model_type = ""
|
| 127 |
-
subfolders = []
|
| 128 |
-
version_strs = []
|
| 129 |
-
if r:
|
| 130 |
-
model_info, model_name, model_type, subfolders, version_strs = r
|
| 131 |
-
|
| 132 |
-
return [model_info, model_name, model_type, dl_subfolder_drop.update(choices=subfolders), dl_version_drop.update(choices=version_strs)]
|
| 133 |
|
| 134 |
# ====UI====
|
| 135 |
-
with gr.Blocks(
|
| 136 |
-
|
| 137 |
-
|
| 138 |
-
|
| 139 |
-
|
| 140 |
-
# session data
|
| 141 |
-
dl_model_info = gr.State({})
|
| 142 |
-
|
| 143 |
-
|
| 144 |
|
|
|
|
| 145 |
with gr.Box(elem_classes="ch_box"):
|
| 146 |
-
|
| 147 |
-
gr.Markdown("### Scan Models for Civitai")
|
| 148 |
-
with gr.Row():
|
| 149 |
-
scan_model_types_ckbg = gr.CheckboxGroup(choices=model_types, label="Model Types", value=model_types)
|
| 150 |
|
| 151 |
-
# with gr.Row():
|
| 152 |
-
scan_model_civitai_btn = gr.Button(value="Scan", variant="primary", elem_id="ch_scan_model_civitai_btn")
|
| 153 |
-
# with gr.Row():
|
| 154 |
-
scan_model_log_md = gr.Markdown(value="Scanning takes time, just wait. Check console log for detail", elem_id="ch_scan_model_log_md")
|
| 155 |
-
|
| 156 |
-
|
| 157 |
with gr.Box(elem_classes="ch_box"):
|
| 158 |
-
|
| 159 |
-
gr.Markdown("### Get Model Info from Civitai by URL")
|
| 160 |
-
gr.Markdown("Use this when scanning can not find a local model on civitai")
|
| 161 |
-
with gr.Row():
|
| 162 |
-
model_type_drop = gr.Dropdown(choices=model_types, label="Model Type", value="ckp", multiselect=False)
|
| 163 |
-
empty_info_only_ckb = gr.Checkbox(label="Only Show Models have no Info", value=False, elem_id="ch_empty_info_only_ckb", elem_classes="ch_vpadding")
|
| 164 |
-
model_name_drop = gr.Dropdown(choices=no_info_model_names, label="Model", value="ckp", multiselect=False)
|
| 165 |
-
|
| 166 |
-
model_url_or_id_txtbox = gr.Textbox(label="Civitai URL", lines=1, value="")
|
| 167 |
-
get_civitai_model_info_by_id_btn = gr.Button(value="Get Model Info from Civitai", variant="primary")
|
| 168 |
-
get_model_by_id_log_md = gr.Markdown("")
|
| 169 |
|
| 170 |
with gr.Box(elem_classes="ch_box"):
|
| 171 |
-
|
| 172 |
-
gr.Markdown("### Download Model")
|
| 173 |
-
with gr.Row():
|
| 174 |
-
dl_model_url_or_id_txtbox = gr.Textbox(label="Civitai URL", lines=1, value="")
|
| 175 |
-
dl_model_info_btn = gr.Button(value="1. Get Model Info by Civitai Url", variant="primary")
|
| 176 |
-
|
| 177 |
-
gr.Markdown(value="2. Pick Subfolder and Model Version")
|
| 178 |
-
with gr.Row():
|
| 179 |
-
dl_model_name_txtbox = gr.Textbox(label="Model Name", interactive=False, lines=1, value="")
|
| 180 |
-
dl_model_type_txtbox = gr.Textbox(label="Model Type", interactive=False, lines=1, value="")
|
| 181 |
-
dl_subfolder_drop = gr.Dropdown(choices=[], label="Sub-folder", value="", interactive=True, multiselect=False)
|
| 182 |
-
dl_version_drop = gr.Dropdown(choices=[], label="Model Version", value="", interactive=True, multiselect=False)
|
| 183 |
-
dl_all_ckb = gr.Checkbox(label="Download All files", value=False, elem_id="ch_dl_all_ckb", elem_classes="ch_vpadding")
|
| 184 |
-
|
| 185 |
-
dl_civitai_model_by_id_btn = gr.Button(value="3. Download Model", variant="primary")
|
| 186 |
-
dl_log_md = gr.Markdown(value="Check Console log for Downloading Status")
|
| 187 |
|
| 188 |
with gr.Box(elem_classes="ch_box"):
|
| 189 |
-
|
| 190 |
-
gr.Markdown("### Check models' new version")
|
| 191 |
-
with gr.Row():
|
| 192 |
-
model_types_ckbg = gr.CheckboxGroup(choices=model_types, label="Model Types", value=["lora"])
|
| 193 |
-
check_models_new_version_btn = gr.Button(value="Check New Version from Civitai", variant="primary")
|
| 194 |
-
|
| 195 |
-
check_models_new_version_log_md = gr.HTML("It takes time, just wait. Check console log for detail")
|
| 196 |
|
| 197 |
with gr.Box(elem_classes="ch_box"):
|
| 198 |
-
|
| 199 |
-
gr.Markdown("### Other")
|
| 200 |
-
# save_setting_btn = gr.Button(value="Save Setting")
|
| 201 |
-
gr.Markdown(value="Settings are moved into Settings Tab->Civitai Helper section")
|
| 202 |
-
|
| 203 |
|
| 204 |
# ====Footer====
|
| 205 |
-
gr.
|
| 206 |
|
| 207 |
# ====hidden component for js, not in any tab====
|
| 208 |
-
js_msg_txtbox
|
| 209 |
-
py_msg_txtbox = gr.Textbox(
|
| 210 |
-
|
| 211 |
-
|
| 212 |
-
|
| 213 |
-
|
| 214 |
-
|
| 215 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 216 |
|
| 217 |
# ====events====
|
| 218 |
-
# Scan Models for Civitai
|
| 219 |
-
scan_model_civitai_btn.click(scan_model, inputs=[scan_model_types_ckbg], outputs=scan_model_log_md)
|
| 220 |
-
|
| 221 |
-
# Get Civitai Model Info by Model Page URL
|
| 222 |
-
model_type_drop.change(get_model_names_by_input, inputs=[model_type_drop, empty_info_only_ckb], outputs=model_name_drop)
|
| 223 |
-
empty_info_only_ckb.change(get_model_names_by_input, inputs=[model_type_drop, empty_info_only_ckb], outputs=model_name_drop)
|
| 224 |
-
|
| 225 |
-
get_civitai_model_info_by_id_btn.click(get_model_info_by_input, inputs=[model_type_drop, model_name_drop, model_url_or_id_txtbox], outputs=get_model_by_id_log_md)
|
| 226 |
-
|
| 227 |
-
# Download Model
|
| 228 |
-
dl_model_info_btn.click(get_model_info_by_url, inputs=dl_model_url_or_id_txtbox, outputs=[dl_model_info, dl_model_name_txtbox, dl_model_type_txtbox, dl_subfolder_drop, dl_version_drop])
|
| 229 |
-
dl_civitai_model_by_id_btn.click(dl_model_by_input, inputs=[dl_model_info, dl_model_type_txtbox, dl_subfolder_drop, dl_version_drop, dl_all_ckb], outputs=dl_log_md)
|
| 230 |
-
|
| 231 |
-
# Check models' new version
|
| 232 |
-
check_models_new_version_btn.click(model_action_civitai.check_models_new_version_to_md, inputs=model_types_ckbg, outputs=check_models_new_version_log_md)
|
| 233 |
-
|
| 234 |
# js action
|
| 235 |
-
js_open_url_btn.click(
|
| 236 |
-
|
| 237 |
-
|
| 238 |
-
|
| 239 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 240 |
|
| 241 |
# the third parameter is the element id on html, with a "tab_" as prefix
|
| 242 |
-
return (civitai_helper
|
| 243 |
-
|
| 244 |
|
| 245 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 246 |
|
| 247 |
|
| 248 |
script_callbacks.on_ui_settings(on_ui_settings)
|
| 249 |
script_callbacks.on_ui_tabs(on_ui_tabs)
|
| 250 |
-
|
| 251 |
-
|
| 252 |
-
|
|
|
|
| 1 |
+
""" -*- coding: UTF-8 -*-
|
| 2 |
+
This extension can help you manage your models from civitai.
|
| 3 |
+
It can download preview, add trigger words, open model page and use the prompt from preview image
|
| 4 |
+
repo: https://github.com/butaixianran/
|
| 5 |
+
"""
|
| 6 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 7 |
import os
|
| 8 |
+
import gradio as gr
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 9 |
import modules
|
| 10 |
+
from modules import scripts
|
| 11 |
from modules import shared
|
| 12 |
+
from modules import script_callbacks
|
| 13 |
from scripts.ch_lib import model
|
| 14 |
from scripts.ch_lib import js_action_civitai
|
|
|
|
| 15 |
from scripts.ch_lib import civitai
|
| 16 |
from scripts.ch_lib import util
|
| 17 |
+
from scripts import sections
|
| 18 |
|
| 19 |
# init
|
|
|
|
| 20 |
# root path
|
| 21 |
+
ROOT_PATH = os.getcwd()
|
| 22 |
|
| 23 |
# extension path
|
| 24 |
+
EXTENSION_PATH = scripts.basedir()
|
| 25 |
+
|
| 26 |
+
# default hidden values for civitai helper buttons
|
| 27 |
+
BUTTONS = {
|
| 28 |
+
"replace_preview_button": False,
|
| 29 |
+
"open_url_button": False,
|
| 30 |
+
"add_trigger_words_button": False,
|
| 31 |
+
"add_preview_prompt_button": False,
|
| 32 |
+
"rename_model_button": False,
|
| 33 |
+
"remove_model_button": False,
|
| 34 |
+
}
|
| 35 |
|
| 36 |
model.get_custom_model_folder()
|
| 37 |
|
| 38 |
+
def update_proxy():
|
| 39 |
+
""" Set proxy, allow for changes at runtime """
|
| 40 |
+
proxy = util.get_opts("ch_proxy")
|
| 41 |
+
|
| 42 |
+
util.printD(f"Set Proxy: {proxy}")
|
| 43 |
+
if proxy:
|
| 44 |
+
util.PROXIES["http"] = proxy
|
| 45 |
+
util.PROXIES["https"] = proxy
|
| 46 |
+
return
|
| 47 |
+
|
| 48 |
+
util.PROXIES["http"] = None
|
| 49 |
+
util.PROXIES["https"] = None
|
| 50 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 51 |
|
| 52 |
def on_ui_tabs():
|
| 53 |
# init
|
| 54 |
# init_py_msg = {
|
| 55 |
# # relative extension path
|
| 56 |
+
# "EXTENSION_PATH": util.get_relative_path(EXTENSION_PATH, ROOT_PATH),
|
| 57 |
# }
|
| 58 |
# init_py_msg_str = json.dumps(init_py_msg)
|
| 59 |
|
| 60 |
+
try:
|
| 61 |
+
BUTTONS["add_trigger_words_button"] = util.newer_version(
|
| 62 |
+
util.webui_version(), '1.5.0', allow_equal=True
|
| 63 |
+
)
|
| 64 |
+
except ValueError: # packaging.version.InvalidVersion
|
| 65 |
+
BUTTONS["add_trigger_words_button"] = False
|
| 66 |
|
| 67 |
# get prompt textarea
|
| 68 |
# check modules/ui.py, search for txt2img_paste_fields
|
|
|
|
| 72 |
img2img_prompt = modules.ui.img2img_paste_fields[0][0]
|
| 73 |
img2img_neg_prompt = modules.ui.img2img_paste_fields[1][0]
|
| 74 |
|
| 75 |
+
# Used by some elements to pass messages to python
|
| 76 |
+
js_msg_txtbox = gr.Textbox(
|
| 77 |
+
label="Request Msg From Js",
|
| 78 |
+
visible=False,
|
| 79 |
+
lines=1,
|
| 80 |
+
value="",
|
| 81 |
+
elem_id="ch_js_msg_txtbox"
|
| 82 |
+
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 83 |
|
| 84 |
# ====UI====
|
| 85 |
+
with gr.Blocks(
|
| 86 |
+
analytics_enabled=False
|
| 87 |
+
) as civitai_helper:
|
| 88 |
+
# with gr.Blocks(css=".block.padded {padding: 10px !important}") as civitai_helper:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 89 |
|
| 90 |
+
# init
|
| 91 |
with gr.Box(elem_classes="ch_box"):
|
| 92 |
+
sections.scan_models_section()
|
|
|
|
|
|
|
|
|
|
| 93 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 94 |
with gr.Box(elem_classes="ch_box"):
|
| 95 |
+
sections.get_model_info_by_url_section()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 96 |
|
| 97 |
with gr.Box(elem_classes="ch_box"):
|
| 98 |
+
sections.download_section()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 99 |
|
| 100 |
with gr.Box(elem_classes="ch_box"):
|
| 101 |
+
sections.scan_for_duplicates_section()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 102 |
|
| 103 |
with gr.Box(elem_classes="ch_box"):
|
| 104 |
+
sections.check_new_versions_section(js_msg_txtbox)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 105 |
|
| 106 |
# ====Footer====
|
| 107 |
+
gr.HTML(f"<center>{util.SHORT_NAME} version: {util.VERSION}</center>")
|
| 108 |
|
| 109 |
# ====hidden component for js, not in any tab====
|
| 110 |
+
js_msg_txtbox.render()
|
| 111 |
+
py_msg_txtbox = gr.Textbox(
|
| 112 |
+
label="Response Msg From Python",
|
| 113 |
+
visible=False,
|
| 114 |
+
lines=1,
|
| 115 |
+
value="",
|
| 116 |
+
elem_id="ch_py_msg_txtbox"
|
| 117 |
+
)
|
| 118 |
+
|
| 119 |
+
js_open_url_btn = gr.Button(
|
| 120 |
+
value="Open Model Url",
|
| 121 |
+
visible=False,
|
| 122 |
+
elem_id="ch_js_open_url_btn"
|
| 123 |
+
)
|
| 124 |
+
js_add_trigger_words_btn = gr.Button(
|
| 125 |
+
value="Add Trigger Words",
|
| 126 |
+
visible=False,
|
| 127 |
+
elem_id="ch_js_add_trigger_words_btn"
|
| 128 |
+
)
|
| 129 |
+
js_use_preview_prompt_btn = gr.Button(
|
| 130 |
+
value="Use Prompt from Preview Image",
|
| 131 |
+
visible=False,
|
| 132 |
+
elem_id="ch_js_use_preview_prompt_btn"
|
| 133 |
+
)
|
| 134 |
+
js_rename_card_btn = gr.Button(
|
| 135 |
+
value="Rename Card",
|
| 136 |
+
visible=False,
|
| 137 |
+
elem_id="ch_js_rename_card_btn"
|
| 138 |
+
)
|
| 139 |
+
js_remove_card_btn = gr.Button(
|
| 140 |
+
value="Remove Card",
|
| 141 |
+
visible=False,
|
| 142 |
+
elem_id="ch_js_remove_card_btn"
|
| 143 |
+
)
|
| 144 |
|
| 145 |
# ====events====
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 146 |
# js action
|
| 147 |
+
js_open_url_btn.click(
|
| 148 |
+
js_action_civitai.open_model_url,
|
| 149 |
+
inputs=[js_msg_txtbox],
|
| 150 |
+
outputs=py_msg_txtbox
|
| 151 |
+
)
|
| 152 |
+
js_add_trigger_words_btn.click(
|
| 153 |
+
js_action_civitai.add_trigger_words,
|
| 154 |
+
inputs=[js_msg_txtbox],
|
| 155 |
+
outputs=[
|
| 156 |
+
txt2img_prompt, img2img_prompt
|
| 157 |
+
]
|
| 158 |
+
)
|
| 159 |
+
js_use_preview_prompt_btn.click(
|
| 160 |
+
js_action_civitai.use_preview_image_prompt,
|
| 161 |
+
inputs=[js_msg_txtbox],
|
| 162 |
+
outputs=[
|
| 163 |
+
txt2img_prompt, txt2img_neg_prompt,
|
| 164 |
+
img2img_prompt, img2img_neg_prompt
|
| 165 |
+
]
|
| 166 |
+
)
|
| 167 |
+
js_rename_card_btn.click(
|
| 168 |
+
js_action_civitai.rename_model_by_path,
|
| 169 |
+
inputs=[js_msg_txtbox],
|
| 170 |
+
outputs=py_msg_txtbox
|
| 171 |
+
)
|
| 172 |
+
js_remove_card_btn.click(
|
| 173 |
+
js_action_civitai.remove_model_by_path,
|
| 174 |
+
inputs=[js_msg_txtbox],
|
| 175 |
+
outputs=py_msg_txtbox
|
| 176 |
+
)
|
| 177 |
|
| 178 |
# the third parameter is the element id on html, with a "tab_" as prefix
|
| 179 |
+
return ((civitai_helper, "Civitai Helper", "civitai_helper"),)
|
|
|
|
| 180 |
|
| 181 |
|
| 182 |
+
def on_ui_settings():
|
| 183 |
+
section = ('civitai_helper', "Civitai Helper")
|
| 184 |
+
shared.opts.add_option(
|
| 185 |
+
"ch_civiai_api_key",
|
| 186 |
+
shared.OptionInfo(
|
| 187 |
+
"",
|
| 188 |
+
(
|
| 189 |
+
"API key for authenticating with Civitai. "
|
| 190 |
+
"This is required to download some models. "
|
| 191 |
+
"See Wiki for more details."
|
| 192 |
+
),
|
| 193 |
+
gr.Textbox,
|
| 194 |
+
{"interactive": True, "max_lines": 1},
|
| 195 |
+
section=section
|
| 196 |
+
).link(
|
| 197 |
+
"Wiki",
|
| 198 |
+
"https://github.com/zixaphir/Stable-Diffusion-Webui-Civitai-Helper/wiki/Civitai-API-Key"
|
| 199 |
+
)
|
| 200 |
+
)
|
| 201 |
+
shared.opts.add_option(
|
| 202 |
+
"ch_dl_lyco_to_lora",
|
| 203 |
+
shared.OptionInfo(
|
| 204 |
+
False,
|
| 205 |
+
(
|
| 206 |
+
"Save LyCORIS models to Lora directory. Do not use this if you are on "
|
| 207 |
+
"older versions of webui or you use an extension that handles LyCORIS "
|
| 208 |
+
"models."
|
| 209 |
+
),
|
| 210 |
+
gr.Checkbox,
|
| 211 |
+
{"interactive": True},
|
| 212 |
+
section=section)
|
| 213 |
+
)
|
| 214 |
+
shared.opts.add_option(
|
| 215 |
+
"ch_open_url_with_js",
|
| 216 |
+
shared.OptionInfo(
|
| 217 |
+
True,
|
| 218 |
+
(
|
| 219 |
+
"Open model Url on the user's client side, rather than server side. "
|
| 220 |
+
"If you are running WebUI locally, disabling this may open URLs in your "
|
| 221 |
+
"default internet browser if it is different than the one you are running "
|
| 222 |
+
"WebUI in"
|
| 223 |
+
),
|
| 224 |
+
gr.Checkbox,
|
| 225 |
+
{"interactive": True},
|
| 226 |
+
section=section
|
| 227 |
+
)
|
| 228 |
+
)
|
| 229 |
+
shared.opts.add_option(
|
| 230 |
+
"ch_hide_buttons",
|
| 231 |
+
shared.OptionInfo(
|
| 232 |
+
[x for x, y in BUTTONS.items() if y],
|
| 233 |
+
"Hide checked Civitai Helper buttons on model cards",
|
| 234 |
+
gr.CheckboxGroup,
|
| 235 |
+
{"choices": list(BUTTONS)},
|
| 236 |
+
section=section
|
| 237 |
+
)
|
| 238 |
+
)
|
| 239 |
+
shared.opts.add_option(
|
| 240 |
+
"ch_always_display",
|
| 241 |
+
shared.OptionInfo(
|
| 242 |
+
False,
|
| 243 |
+
"Always Display Buttons on model cards",
|
| 244 |
+
gr.Checkbox,
|
| 245 |
+
{"interactive": True},
|
| 246 |
+
section=section
|
| 247 |
+
)
|
| 248 |
+
)
|
| 249 |
+
shared.opts.add_option(
|
| 250 |
+
"ch_show_btn_on_thumb",
|
| 251 |
+
shared.OptionInfo(
|
| 252 |
+
True,
|
| 253 |
+
"Show Button On Thumb Mode in SD webui versions before 1.5.0",
|
| 254 |
+
gr.Checkbox,
|
| 255 |
+
{"interactive": True},
|
| 256 |
+
section=section
|
| 257 |
+
)
|
| 258 |
+
)
|
| 259 |
+
shared.opts.add_option(
|
| 260 |
+
"ch_max_size_preview",
|
| 261 |
+
shared.OptionInfo(
|
| 262 |
+
True,
|
| 263 |
+
"Download Max Size Preview",
|
| 264 |
+
gr.Checkbox,
|
| 265 |
+
{"interactive": True},
|
| 266 |
+
section=section
|
| 267 |
+
)
|
| 268 |
+
)
|
| 269 |
+
shared.opts.add_option(
|
| 270 |
+
"ch_nsfw_preview_threshold",
|
| 271 |
+
shared.OptionInfo(
|
| 272 |
+
civitai.NSFW_LEVELS[-1], # Allow all
|
| 273 |
+
util.dedent(
|
| 274 |
+
"""
|
| 275 |
+
Block NSFW images of a certain threshold and higher.
|
| 276 |
+
Civitai marks all images for NSFW models as also being NSFW.
|
| 277 |
+
These ratings do not seem to be explicitly defined on Civitai's
|
| 278 |
+
end, but "Soft" seems to be suggestive, with NSFW elements but
|
| 279 |
+
not explicit nudity, "Mature" seems to include nudity but not
|
| 280 |
+
always, and "X" seems to be explicitly adult content.
|
| 281 |
+
"""
|
| 282 |
+
).strip().replace("\n", " "),
|
| 283 |
+
gr.Dropdown,
|
| 284 |
+
{
|
| 285 |
+
"choices": civitai.NSFW_LEVELS[1:],
|
| 286 |
+
"interactive": True
|
| 287 |
+
},
|
| 288 |
+
section=section
|
| 289 |
+
)
|
| 290 |
+
)
|
| 291 |
+
shared.opts.add_option(
|
| 292 |
+
"ch_dl_webui_metadata",
|
| 293 |
+
shared.OptionInfo(
|
| 294 |
+
True,
|
| 295 |
+
"Also add data for WebUI metadata editor",
|
| 296 |
+
gr.Checkbox,
|
| 297 |
+
{"interactive": True},
|
| 298 |
+
section=section)
|
| 299 |
+
)
|
| 300 |
+
shared.opts.add_option(
|
| 301 |
+
"ch_proxy",
|
| 302 |
+
shared.OptionInfo(
|
| 303 |
+
"",
|
| 304 |
+
"Proxy to use for fetching models and model data. Format: http://127.0.0.1:port",
|
| 305 |
+
gr.Textbox,
|
| 306 |
+
{"interactive": True, "max_lines": 1},
|
| 307 |
+
section=section)
|
| 308 |
+
)
|
| 309 |
+
shared.opts.onchange(
|
| 310 |
+
"ch_proxy",
|
| 311 |
+
update_proxy
|
| 312 |
+
)
|
| 313 |
|
| 314 |
|
| 315 |
script_callbacks.on_ui_settings(on_ui_settings)
|
| 316 |
script_callbacks.on_ui_tabs(on_ui_tabs)
|
|
|
|
|
|
|
|
|
extensions/Stable-Diffusion-Webui-Civitai-Helper/scripts/sections.py
ADDED
|
@@ -0,0 +1,693 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
""" -*- coding: UTF-8 -*-
|
| 2 |
+
Sections for civitai_helper tab.
|
| 3 |
+
"""
|
| 4 |
+
|
| 5 |
+
import gradio as gr
|
| 6 |
+
from scripts.ch_lib import model
|
| 7 |
+
from scripts.ch_lib import js_action_civitai
|
| 8 |
+
from scripts.ch_lib import model_action_civitai
|
| 9 |
+
from scripts.ch_lib import civitai
|
| 10 |
+
from scripts.ch_lib import duplicate_check
|
| 11 |
+
from scripts.ch_lib import util
|
| 12 |
+
|
| 13 |
+
model_types = list(model.folders.keys())
|
| 14 |
+
|
| 15 |
+
def scan_models_section():
|
| 16 |
+
""" Scan Models Section """
|
| 17 |
+
with gr.Row():
|
| 18 |
+
gr.Markdown("### Scan Models for Civitai")
|
| 19 |
+
with gr.Row():
|
| 20 |
+
with gr.Column():
|
| 21 |
+
scan_model_types_drop = gr.CheckboxGroup(
|
| 22 |
+
choices=model_types,
|
| 23 |
+
label="Model Types",
|
| 24 |
+
value=model_types
|
| 25 |
+
)
|
| 26 |
+
nsfw_preview_scan_drop = gr.Dropdown(
|
| 27 |
+
label="Block NSFW Level Above",
|
| 28 |
+
choices=civitai.NSFW_LEVELS[1:],
|
| 29 |
+
value=util.get_opts("ch_nsfw_preview_threshold"),
|
| 30 |
+
elem_id="ch_nsfw_preview_scan_drop"
|
| 31 |
+
)
|
| 32 |
+
with gr.Row():
|
| 33 |
+
with gr.Column():
|
| 34 |
+
refetch_old_ckb = gr.Checkbox(
|
| 35 |
+
label="Replace Old Metadata Formats*",
|
| 36 |
+
value=False,
|
| 37 |
+
elem_id="ch_refetch_old_ckb"
|
| 38 |
+
)
|
| 39 |
+
gr.HTML("""
|
| 40 |
+
* [<a href=https://github.com/zixaphir/Stable-Diffusion-Webui-Civitai-Helper/wiki/Metadata-Format-Changes>wiki</a>] Do not use this option if you have made changes with the metadata editor without backing up your data!!<br><br>
|
| 41 |
+
""")
|
| 42 |
+
|
| 43 |
+
with gr.Column():
|
| 44 |
+
scan_model_civitai_btn = gr.Button(
|
| 45 |
+
value="Scan",
|
| 46 |
+
variant="primary",
|
| 47 |
+
elem_id="ch_scan_model_civitai_btn"
|
| 48 |
+
)
|
| 49 |
+
|
| 50 |
+
scan_civitai_info_image_meta_btn = gr.Button(
|
| 51 |
+
value="Update image generation information (Experimental)",
|
| 52 |
+
variant="primary",
|
| 53 |
+
elem_id="ch_Scan_civitai_info_image_meta_btn"
|
| 54 |
+
)
|
| 55 |
+
|
| 56 |
+
with gr.Row():
|
| 57 |
+
scan_model_log_md = gr.Markdown(
|
| 58 |
+
value="Scanning takes time, just wait. Check console log for details",
|
| 59 |
+
elem_id="ch_scan_model_log_md"
|
| 60 |
+
)
|
| 61 |
+
|
| 62 |
+
# ====events====
|
| 63 |
+
scan_model_civitai_btn.click(
|
| 64 |
+
model_action_civitai.scan_model,
|
| 65 |
+
inputs=[
|
| 66 |
+
scan_model_types_drop,
|
| 67 |
+
nsfw_preview_scan_drop, refetch_old_ckb
|
| 68 |
+
],
|
| 69 |
+
outputs=scan_model_log_md
|
| 70 |
+
)
|
| 71 |
+
|
| 72 |
+
scan_civitai_info_image_meta_btn.click(
|
| 73 |
+
model.scan_civitai_info_image_meta,
|
| 74 |
+
outputs=scan_model_log_md
|
| 75 |
+
)
|
| 76 |
+
|
| 77 |
+
def get_model_info_by_url_section():
|
| 78 |
+
""" Get Civitai Model Info by Model Page URL Section """
|
| 79 |
+
|
| 80 |
+
def get_model_names_by_input(model_type, empty_info_only):
|
| 81 |
+
names = civitai.get_model_names_by_input(model_type, empty_info_only)
|
| 82 |
+
return model_name_drop.update(choices=names)
|
| 83 |
+
|
| 84 |
+
no_info_model_names = civitai.get_model_names_by_input("ckp", False)
|
| 85 |
+
|
| 86 |
+
with gr.Column():
|
| 87 |
+
gr.Markdown("### Get Model Info from Civitai by URL")
|
| 88 |
+
gr.Markdown("Use this when scanning can not find a local model on civitai")
|
| 89 |
+
with gr.Row():
|
| 90 |
+
with gr.Column(scale=2):
|
| 91 |
+
model_type_drop = gr.Dropdown(
|
| 92 |
+
choices=model_types,
|
| 93 |
+
label="Model Type",
|
| 94 |
+
value="ckp",
|
| 95 |
+
multiselect=False,
|
| 96 |
+
elem_classes="ch_vpadding"
|
| 97 |
+
)
|
| 98 |
+
with gr.Column(scale=1):
|
| 99 |
+
empty_info_only_ckb = gr.Checkbox(
|
| 100 |
+
label="Only Show Models have no Info",
|
| 101 |
+
value=False,
|
| 102 |
+
elem_id="ch_empty_info_only_ckb",
|
| 103 |
+
elem_classes="ch_vpadding"
|
| 104 |
+
)
|
| 105 |
+
with gr.Row():
|
| 106 |
+
with gr.Column(scale=2):
|
| 107 |
+
model_name_drop = gr.Dropdown(
|
| 108 |
+
choices=no_info_model_names,
|
| 109 |
+
label="Model",
|
| 110 |
+
value="",
|
| 111 |
+
multiselect=False
|
| 112 |
+
)
|
| 113 |
+
with gr.Column(scale=1):
|
| 114 |
+
nsfw_preview_url_drop = gr.Dropdown(
|
| 115 |
+
label="Block NSFW Level Above",
|
| 116 |
+
choices=civitai.NSFW_LEVELS[1:],
|
| 117 |
+
value=util.get_opts("ch_nsfw_preview_threshold"),
|
| 118 |
+
elem_id="ch_nsfw_preview_url_drop"
|
| 119 |
+
)
|
| 120 |
+
with gr.Row():
|
| 121 |
+
with gr.Column(scale=2, elem_classes="justify-bottom"):
|
| 122 |
+
model_url_or_id_txtbox = gr.Textbox(
|
| 123 |
+
label="Civitai URL",
|
| 124 |
+
lines=1,
|
| 125 |
+
value=""
|
| 126 |
+
)
|
| 127 |
+
with gr.Column(scale=1, elem_classes="justify-bottom"):
|
| 128 |
+
get_civitai_model_info_by_id_btn = gr.Button(
|
| 129 |
+
value="Get Model Info from Civitai",
|
| 130 |
+
variant="primary"
|
| 131 |
+
)
|
| 132 |
+
|
| 133 |
+
get_model_by_id_log_md = gr.Markdown("")
|
| 134 |
+
|
| 135 |
+
# ====events====
|
| 136 |
+
model_type_drop.change(
|
| 137 |
+
get_model_names_by_input,
|
| 138 |
+
inputs=[
|
| 139 |
+
model_type_drop, empty_info_only_ckb
|
| 140 |
+
],
|
| 141 |
+
outputs=model_name_drop
|
| 142 |
+
)
|
| 143 |
+
empty_info_only_ckb.change(
|
| 144 |
+
get_model_names_by_input,
|
| 145 |
+
inputs=[
|
| 146 |
+
model_type_drop, empty_info_only_ckb
|
| 147 |
+
],
|
| 148 |
+
outputs=model_name_drop
|
| 149 |
+
)
|
| 150 |
+
|
| 151 |
+
get_civitai_model_info_by_id_btn.click(
|
| 152 |
+
model_action_civitai.get_model_info_by_input,
|
| 153 |
+
inputs=[
|
| 154 |
+
model_type_drop, model_name_drop,
|
| 155 |
+
model_url_or_id_txtbox, nsfw_preview_url_drop
|
| 156 |
+
],
|
| 157 |
+
outputs=get_model_by_id_log_md
|
| 158 |
+
)
|
| 159 |
+
|
| 160 |
+
def download_section():
|
| 161 |
+
""" Download Models Section """
|
| 162 |
+
|
| 163 |
+
model_filetypes = civitai.FILE_TYPES
|
| 164 |
+
file_elems = {}
|
| 165 |
+
|
| 166 |
+
dl_state = gr.State({
|
| 167 |
+
"model_info": {},
|
| 168 |
+
"filenames": {
|
| 169 |
+
# dl_version_str: filename,
|
| 170 |
+
},
|
| 171 |
+
"previews": {
|
| 172 |
+
# dl_version_str: [{url: url, nsfw: nsfw}],
|
| 173 |
+
},
|
| 174 |
+
"files": {
|
| 175 |
+
# dl_version_str: {
|
| 176 |
+
# Model: bool,
|
| 177 |
+
# Config: bool,
|
| 178 |
+
# ...
|
| 179 |
+
# }
|
| 180 |
+
},
|
| 181 |
+
"files_count": {
|
| 182 |
+
# dl_version_str: int
|
| 183 |
+
},
|
| 184 |
+
"filtered_previews": []
|
| 185 |
+
})
|
| 186 |
+
|
| 187 |
+
def get_model_info_by_url(url, subfolder, state):
|
| 188 |
+
data = model_action_civitai.get_model_info_by_url(url)
|
| 189 |
+
|
| 190 |
+
if not data:
|
| 191 |
+
return None
|
| 192 |
+
|
| 193 |
+
state = {
|
| 194 |
+
"model_info": {},
|
| 195 |
+
"filenames": {},
|
| 196 |
+
"previews": {},
|
| 197 |
+
"files": {},
|
| 198 |
+
"files_count": {},
|
| 199 |
+
"filtered_previews": []
|
| 200 |
+
}
|
| 201 |
+
|
| 202 |
+
subfolders = sorted(data["subfolders"])
|
| 203 |
+
version_strs = data["version_strs"]
|
| 204 |
+
filenames = data["filenames"]
|
| 205 |
+
|
| 206 |
+
if subfolder == "" or subfolder not in subfolders:
|
| 207 |
+
subfolder = "/"
|
| 208 |
+
|
| 209 |
+
state["model_info"] = data["model_info"]
|
| 210 |
+
state["previews"] = data["previews"]
|
| 211 |
+
|
| 212 |
+
for filename, version in zip(filenames, version_strs):
|
| 213 |
+
state["filenames"][version] = filename
|
| 214 |
+
|
| 215 |
+
for version_files, version in zip(data["files"], version_strs):
|
| 216 |
+
filetypes = state["files"][version] = {}
|
| 217 |
+
files_count = 0
|
| 218 |
+
unhandled_files = []
|
| 219 |
+
for filedata in version_files:
|
| 220 |
+
files_count += 1
|
| 221 |
+
ch_filedata = {
|
| 222 |
+
"id": filedata["id"],
|
| 223 |
+
"name": filedata["name"],
|
| 224 |
+
}
|
| 225 |
+
|
| 226 |
+
if filedata["type"] in model_filetypes:
|
| 227 |
+
filetypes[filedata["type"]] = (True, ch_filedata)
|
| 228 |
+
continue
|
| 229 |
+
|
| 230 |
+
unhandled_files.append(f"{ch_filedata['id']}: {ch_filedata['name']}")
|
| 231 |
+
|
| 232 |
+
if len(unhandled_files) > 0:
|
| 233 |
+
filetypes["unhandled_files"] = "\n".join(unhandled_files)
|
| 234 |
+
else:
|
| 235 |
+
filetypes["unhandled_files"] = None
|
| 236 |
+
|
| 237 |
+
state["files_count"][version] = files_count
|
| 238 |
+
|
| 239 |
+
return [
|
| 240 |
+
state, data["model_name"], data["model_type"],
|
| 241 |
+
dl_subfolder_drop.update(
|
| 242 |
+
choices=subfolders,
|
| 243 |
+
value=subfolder
|
| 244 |
+
),
|
| 245 |
+
dl_version_drop.update(
|
| 246 |
+
choices=version_strs,
|
| 247 |
+
value=version_strs[0]
|
| 248 |
+
),
|
| 249 |
+
files_row.update(
|
| 250 |
+
visible=True
|
| 251 |
+
)
|
| 252 |
+
]
|
| 253 |
+
|
| 254 |
+
def filter_previews(previews, nsfw_preview_url_drop):
|
| 255 |
+
images = []
|
| 256 |
+
for preview in previews:
|
| 257 |
+
if civitai.should_skip(nsfw_preview_url_drop, preview["nsfw"]):
|
| 258 |
+
continue
|
| 259 |
+
if preview["type"] == "image":
|
| 260 |
+
# Civitai added videos as previews, and webui does not like it
|
| 261 |
+
images.append(preview["url"])
|
| 262 |
+
|
| 263 |
+
return images
|
| 264 |
+
|
| 265 |
+
def update_dl_inputs(state, dl_version, nsfw_threshold, dl_preview_index):
|
| 266 |
+
filename = state["filenames"][dl_version]
|
| 267 |
+
|
| 268 |
+
if not filename:
|
| 269 |
+
filename = dl_filename_txtbox.value
|
| 270 |
+
|
| 271 |
+
file_parts = filename.split(".")
|
| 272 |
+
ext = file_parts.pop()
|
| 273 |
+
base = ".".join(file_parts)
|
| 274 |
+
|
| 275 |
+
previews = filter_previews(state["previews"][dl_version], nsfw_threshold)
|
| 276 |
+
state["filtered_previews"] = previews
|
| 277 |
+
|
| 278 |
+
preview = None
|
| 279 |
+
if len(previews) > dl_preview_index:
|
| 280 |
+
preview = previews[dl_preview_index]
|
| 281 |
+
elif len(previews) > 0:
|
| 282 |
+
preview = previews[0]
|
| 283 |
+
|
| 284 |
+
output_add = []
|
| 285 |
+
|
| 286 |
+
for key, elems in file_elems.items():
|
| 287 |
+
filedata = state["files"][dl_version].get(key, False)
|
| 288 |
+
|
| 289 |
+
visible = False
|
| 290 |
+
filename = ""
|
| 291 |
+
if filedata:
|
| 292 |
+
visible = True
|
| 293 |
+
if isinstance(filedata, str):
|
| 294 |
+
filename = ", ".join([
|
| 295 |
+
filedata_str.split(":")[1].strip()
|
| 296 |
+
for filedata_str in filedata.split("\n")
|
| 297 |
+
])
|
| 298 |
+
elif isinstance(filedata, tuple):
|
| 299 |
+
_, data = filedata
|
| 300 |
+
filename = data["name"]
|
| 301 |
+
else:
|
| 302 |
+
raise ValueError(f"Invalid filedata: {filedata}")
|
| 303 |
+
|
| 304 |
+
output_add.append(elems["txtbx"].update(value=filename))
|
| 305 |
+
output_add.append(elems["row"].update(visible=visible))
|
| 306 |
+
|
| 307 |
+
return [
|
| 308 |
+
state,
|
| 309 |
+
dl_filename_txtbox.update(
|
| 310 |
+
value=base
|
| 311 |
+
),
|
| 312 |
+
dl_extension_txtbox.update(
|
| 313 |
+
value=ext
|
| 314 |
+
),
|
| 315 |
+
dl_preview_img.update(
|
| 316 |
+
value=previews
|
| 317 |
+
),
|
| 318 |
+
dl_preview_url.update(
|
| 319 |
+
value=preview
|
| 320 |
+
),
|
| 321 |
+
download_all_row.update(
|
| 322 |
+
visible=(state["files_count"][dl_version] > 1)
|
| 323 |
+
)
|
| 324 |
+
] + output_add
|
| 325 |
+
|
| 326 |
+
def update_dl_files_visibility(dl_all):
|
| 327 |
+
files_chkboxes = []
|
| 328 |
+
for chkbox in ch_dl_model_types_visibility:
|
| 329 |
+
files_chkboxes.append(
|
| 330 |
+
chkbox.update(
|
| 331 |
+
visible=not dl_all
|
| 332 |
+
)
|
| 333 |
+
)
|
| 334 |
+
|
| 335 |
+
return files_chkboxes
|
| 336 |
+
|
| 337 |
+
def update_dl_preview_url(state, dl_preview_index):
|
| 338 |
+
preview_url = state["filtered_previews"][dl_preview_index]
|
| 339 |
+
|
| 340 |
+
return dl_preview_url.update(
|
| 341 |
+
value=preview_url
|
| 342 |
+
)
|
| 343 |
+
|
| 344 |
+
def update_dl_preview_index(evt: gr.SelectData):
|
| 345 |
+
# For some reason, you can't pass gr.SelectData and
|
| 346 |
+
# inputs at the same time. :/
|
| 347 |
+
|
| 348 |
+
return dl_preview_index.update(
|
| 349 |
+
value=evt.index
|
| 350 |
+
)
|
| 351 |
+
|
| 352 |
+
with gr.Row():
|
| 353 |
+
gr.Markdown("### Download Model")
|
| 354 |
+
|
| 355 |
+
with gr.Row():
|
| 356 |
+
with gr.Column(scale=2, elem_id="ch_dl_model_inputs"):
|
| 357 |
+
|
| 358 |
+
gr.Markdown(value="**1. Add URL and retrieve Model Info**")
|
| 359 |
+
|
| 360 |
+
with gr.Row():
|
| 361 |
+
with gr.Column(scale=2, elem_classes="justify-bottom"):
|
| 362 |
+
dl_model_url_or_id_txtbox = gr.Textbox(
|
| 363 |
+
label="Civitai URL",
|
| 364 |
+
lines=1,
|
| 365 |
+
max_lines=1,
|
| 366 |
+
value="",
|
| 367 |
+
placeholder="Model URL or Model ID"
|
| 368 |
+
)
|
| 369 |
+
with gr.Column(elem_classes="justify-bottom"):
|
| 370 |
+
dl_model_info_btn = gr.Button(
|
| 371 |
+
value="Get Model Info by Civitai Url",
|
| 372 |
+
variant="primary"
|
| 373 |
+
)
|
| 374 |
+
|
| 375 |
+
gr.Markdown(value="**2. Pick Subfolder and Model Version**")
|
| 376 |
+
|
| 377 |
+
with gr.Row(elem_classes="ch_grid"):
|
| 378 |
+
dl_model_name_txtbox = gr.Textbox(
|
| 379 |
+
label="Model Name",
|
| 380 |
+
interactive=False,
|
| 381 |
+
lines=1,
|
| 382 |
+
max_lines=1,
|
| 383 |
+
min_width=320,
|
| 384 |
+
value=""
|
| 385 |
+
)
|
| 386 |
+
dl_model_type_txtbox = gr.Textbox(
|
| 387 |
+
label="Model Type",
|
| 388 |
+
interactive=False,
|
| 389 |
+
lines=1,
|
| 390 |
+
max_lines=1,
|
| 391 |
+
min_width=320,
|
| 392 |
+
value=""
|
| 393 |
+
)
|
| 394 |
+
dl_version_drop = gr.Dropdown(
|
| 395 |
+
choices=[],
|
| 396 |
+
label="Model Version",
|
| 397 |
+
value="",
|
| 398 |
+
min_width=320,
|
| 399 |
+
multiselect=False
|
| 400 |
+
)
|
| 401 |
+
dl_subfolder_drop = gr.Dropdown(
|
| 402 |
+
choices=[],
|
| 403 |
+
label="Sub-folder",
|
| 404 |
+
value="",
|
| 405 |
+
min_width=320,
|
| 406 |
+
multiselect=False
|
| 407 |
+
)
|
| 408 |
+
dl_duplicate_drop = gr.Dropdown(
|
| 409 |
+
choices=["Skip", "Overwrite", "Rename New"],
|
| 410 |
+
label="Duplicate File Behavior",
|
| 411 |
+
value="Skip",
|
| 412 |
+
min_width=320,
|
| 413 |
+
multiselect=False
|
| 414 |
+
)
|
| 415 |
+
nsfw_preview_dl_drop = gr.Dropdown(
|
| 416 |
+
label="Block NSFW Level Above",
|
| 417 |
+
choices=civitai.NSFW_LEVELS[1:],
|
| 418 |
+
value=util.get_opts("ch_nsfw_preview_threshold"),
|
| 419 |
+
min_width=320,
|
| 420 |
+
elem_id="ch_nsfw_preview_dl_drop"
|
| 421 |
+
)
|
| 422 |
+
|
| 423 |
+
with gr.Column(
|
| 424 |
+
visible=False,
|
| 425 |
+
variant="panel"
|
| 426 |
+
) as files_row:
|
| 427 |
+
|
| 428 |
+
with gr.Row(variant="compact"):
|
| 429 |
+
gr.Markdown("**Files**")
|
| 430 |
+
|
| 431 |
+
ch_output_add = []
|
| 432 |
+
ch_dl_model_types = []
|
| 433 |
+
ch_dl_model_types_visibility = []
|
| 434 |
+
|
| 435 |
+
for filetype in model_filetypes:
|
| 436 |
+
with gr.Row(
|
| 437 |
+
visible=False,
|
| 438 |
+
equal_height=True,
|
| 439 |
+
) as row:
|
| 440 |
+
file_elems[filetype] = elems = {}
|
| 441 |
+
elems["row"] = row
|
| 442 |
+
|
| 443 |
+
with gr.Column(scale=0, min_width=24, elem_classes="flex-center") as ckb_column:
|
| 444 |
+
elems["ckb"] = filetype_ckb = gr.Checkbox(
|
| 445 |
+
label="",
|
| 446 |
+
value=True,
|
| 447 |
+
min_width=0,
|
| 448 |
+
interactive=(not filetype == "Model")
|
| 449 |
+
)
|
| 450 |
+
with gr.Column(scale=1, min_width=0):
|
| 451 |
+
elems["txtbx"] = gr.Textbox(
|
| 452 |
+
value="",
|
| 453 |
+
interactive=False,
|
| 454 |
+
label=filetype,
|
| 455 |
+
max_lines=1,
|
| 456 |
+
min_width=0
|
| 457 |
+
)
|
| 458 |
+
|
| 459 |
+
ch_dl_model_types_visibility.append(ckb_column)
|
| 460 |
+
ch_dl_model_types.append(filetype_ckb)
|
| 461 |
+
|
| 462 |
+
ch_output_add.append(elems["txtbx"])
|
| 463 |
+
ch_output_add.append(row)
|
| 464 |
+
|
| 465 |
+
with gr.Row(visible=False) as unhandled_files_row:
|
| 466 |
+
file_elems["unhandled_files"] = elems = {}
|
| 467 |
+
elems["row"] = row
|
| 468 |
+
|
| 469 |
+
elems["txtbx"] = gr.Textbox(
|
| 470 |
+
value="",
|
| 471 |
+
interactive=False,
|
| 472 |
+
label="Unhandled Files (Files that we don't know what to do with but will still downloaded with \"Download All Files\")",
|
| 473 |
+
)
|
| 474 |
+
|
| 475 |
+
ch_output_add.append(elems["txtbx"])
|
| 476 |
+
ch_output_add.append(unhandled_files_row)
|
| 477 |
+
|
| 478 |
+
with gr.Row(visible=False) as download_all_row:
|
| 479 |
+
dl_all_ckb = gr.Checkbox(
|
| 480 |
+
label="Download All Files",
|
| 481 |
+
value=False,
|
| 482 |
+
elem_id="ch_dl_all_ckb",
|
| 483 |
+
elem_classes="ch_vpadding"
|
| 484 |
+
)
|
| 485 |
+
|
| 486 |
+
with gr.Column(scale=1, elem_id="ch_preview_col", min_width=512):
|
| 487 |
+
with gr.Row(elem_classes="flex-center"):
|
| 488 |
+
dl_preview_img = gr.Gallery(
|
| 489 |
+
show_label=True,
|
| 490 |
+
label="Preview Image",
|
| 491 |
+
value=None,
|
| 492 |
+
elem_id="ch_dl_preview_img",
|
| 493 |
+
allow_preview=True,
|
| 494 |
+
preview=False,
|
| 495 |
+
object_fit="scale-down"
|
| 496 |
+
)
|
| 497 |
+
dl_preview_url = gr.Textbox(
|
| 498 |
+
value="",
|
| 499 |
+
visible=False,
|
| 500 |
+
elem_id="ch_dl_preview_url"
|
| 501 |
+
)
|
| 502 |
+
dl_preview_index = gr.Number(
|
| 503 |
+
value=0,
|
| 504 |
+
visible=False,
|
| 505 |
+
elem_id="ch_dl_preview_index",
|
| 506 |
+
precision=0
|
| 507 |
+
)
|
| 508 |
+
|
| 509 |
+
with gr.Row():
|
| 510 |
+
with gr.Column(scale=2, elem_classes="justify-bottom"):
|
| 511 |
+
dl_filename_txtbox = gr.Textbox(
|
| 512 |
+
label="Rename Model",
|
| 513 |
+
value="",
|
| 514 |
+
lines=1,
|
| 515 |
+
max_lines=1,
|
| 516 |
+
elem_id="ch_dl_filename_txtbox",
|
| 517 |
+
elem_classes="ch_vpadding"
|
| 518 |
+
)
|
| 519 |
+
dl_extension_txtbox = gr.Textbox(
|
| 520 |
+
label="Model extension",
|
| 521 |
+
value="",
|
| 522 |
+
elem_id="ch_dl_extension_txtbox",
|
| 523 |
+
visible=False
|
| 524 |
+
)
|
| 525 |
+
|
| 526 |
+
with gr.Column(elem_classes="justify-bottom"):
|
| 527 |
+
dl_civitai_model_by_id_btn = gr.Button(
|
| 528 |
+
value="3. Download Model",
|
| 529 |
+
elem_classes="ch_vmargin",
|
| 530 |
+
variant="primary"
|
| 531 |
+
)
|
| 532 |
+
|
| 533 |
+
with gr.Row():
|
| 534 |
+
dl_log_md = gr.Markdown(
|
| 535 |
+
value="Check Console log for Downloading Status"
|
| 536 |
+
)
|
| 537 |
+
|
| 538 |
+
# ====events====
|
| 539 |
+
dl_model_info_btn.click(
|
| 540 |
+
get_model_info_by_url,
|
| 541 |
+
inputs=[
|
| 542 |
+
dl_model_url_or_id_txtbox, dl_subfolder_drop, dl_state
|
| 543 |
+
],
|
| 544 |
+
outputs=[
|
| 545 |
+
dl_state, dl_model_name_txtbox,
|
| 546 |
+
dl_model_type_txtbox, dl_subfolder_drop,
|
| 547 |
+
dl_version_drop, files_row
|
| 548 |
+
]
|
| 549 |
+
)
|
| 550 |
+
|
| 551 |
+
dl_inputs = [
|
| 552 |
+
dl_state, dl_model_type_txtbox,
|
| 553 |
+
dl_subfolder_drop, dl_version_drop,
|
| 554 |
+
dl_filename_txtbox, dl_extension_txtbox,
|
| 555 |
+
dl_all_ckb, nsfw_preview_dl_drop,
|
| 556 |
+
dl_duplicate_drop, dl_preview_url
|
| 557 |
+
] + ch_dl_model_types
|
| 558 |
+
|
| 559 |
+
dl_civitai_model_by_id_btn.click(
|
| 560 |
+
model_action_civitai.dl_model_by_input,
|
| 561 |
+
inputs=dl_inputs,
|
| 562 |
+
outputs=dl_log_md
|
| 563 |
+
)
|
| 564 |
+
|
| 565 |
+
ver_outputs = [
|
| 566 |
+
dl_state, dl_filename_txtbox, dl_extension_txtbox,
|
| 567 |
+
dl_preview_img, dl_preview_url, download_all_row,
|
| 568 |
+
] + ch_output_add
|
| 569 |
+
|
| 570 |
+
dl_version_drop.change(
|
| 571 |
+
update_dl_inputs,
|
| 572 |
+
inputs=[dl_state, dl_version_drop, nsfw_preview_dl_drop, dl_preview_index],
|
| 573 |
+
outputs=ver_outputs
|
| 574 |
+
)
|
| 575 |
+
dl_all_ckb.change(
|
| 576 |
+
update_dl_files_visibility,
|
| 577 |
+
inputs=dl_all_ckb,
|
| 578 |
+
outputs=ch_dl_model_types_visibility
|
| 579 |
+
)
|
| 580 |
+
nsfw_preview_dl_drop.change(
|
| 581 |
+
update_dl_inputs,
|
| 582 |
+
inputs=[dl_version_drop, dl_state, nsfw_preview_dl_drop],
|
| 583 |
+
outputs=ver_outputs
|
| 584 |
+
)
|
| 585 |
+
# Gradio has so many issues with Gradio.Gallery...
|
| 586 |
+
dl_preview_img.select(
|
| 587 |
+
update_dl_preview_index,
|
| 588 |
+
None,
|
| 589 |
+
dl_preview_index
|
| 590 |
+
)
|
| 591 |
+
dl_preview_index.change(
|
| 592 |
+
update_dl_preview_url,
|
| 593 |
+
[dl_state, dl_preview_index],
|
| 594 |
+
dl_preview_url
|
| 595 |
+
)
|
| 596 |
+
|
| 597 |
+
|
| 598 |
+
def scan_for_duplicates_section():
|
| 599 |
+
""" Scan Duplicate Models Section """
|
| 600 |
+
with gr.Column():
|
| 601 |
+
gr.Markdown("### Scan for duplicate models")
|
| 602 |
+
with gr.Row():
|
| 603 |
+
with gr.Column():
|
| 604 |
+
scan_dup_model_types_drop = gr.CheckboxGroup(
|
| 605 |
+
choices=model_types,
|
| 606 |
+
label="Model Types",
|
| 607 |
+
value=model_types
|
| 608 |
+
)
|
| 609 |
+
with gr.Row():
|
| 610 |
+
with gr.Column(scale=2):
|
| 611 |
+
cached_hash_ckb = gr.Checkbox(
|
| 612 |
+
label="Use Hash from Metadata (May have false-positives but can be useful if you've pruned models)",
|
| 613 |
+
value=False,
|
| 614 |
+
elem_id="ch_cached_hash_ckb"
|
| 615 |
+
)
|
| 616 |
+
with gr.Column():
|
| 617 |
+
scan_dup_model_btn = gr.Button(
|
| 618 |
+
value="Scan",
|
| 619 |
+
variant="primary",
|
| 620 |
+
elem_id="ch_scan_dup_model_civitai_btn"
|
| 621 |
+
)
|
| 622 |
+
|
| 623 |
+
# with gr.Row():
|
| 624 |
+
scan_dup_model_log_md = gr.HTML(
|
| 625 |
+
value="Scanning takes time, just wait. Check console log for details",
|
| 626 |
+
elem_id="ch_scan_dup_model_log_md"
|
| 627 |
+
)
|
| 628 |
+
|
| 629 |
+
# ====events====
|
| 630 |
+
scan_dup_model_btn.click(
|
| 631 |
+
duplicate_check.scan_for_dups,
|
| 632 |
+
inputs=[
|
| 633 |
+
scan_dup_model_types_drop,
|
| 634 |
+
cached_hash_ckb
|
| 635 |
+
],
|
| 636 |
+
outputs=scan_dup_model_log_md
|
| 637 |
+
)
|
| 638 |
+
|
| 639 |
+
def check_new_versions_section(js_msg_txtbox):
|
| 640 |
+
""" Check models' new version section """
|
| 641 |
+
|
| 642 |
+
with gr.Column():
|
| 643 |
+
gr.Markdown("### Check models' new version")
|
| 644 |
+
with gr.Row():
|
| 645 |
+
with gr.Column(scale=2):
|
| 646 |
+
model_types_ckbg = gr.CheckboxGroup(
|
| 647 |
+
choices=model_types,
|
| 648 |
+
label="Model Types",
|
| 649 |
+
value=[
|
| 650 |
+
"ti", "hyper", "ckp", "lora", "lycoris"
|
| 651 |
+
]
|
| 652 |
+
)
|
| 653 |
+
nsfw_preview_update_drop = gr.Dropdown(
|
| 654 |
+
label="Block NSFW Level Above",
|
| 655 |
+
choices=civitai.NSFW_LEVELS[1:],
|
| 656 |
+
value=util.get_opts("ch_nsfw_preview_threshold"),
|
| 657 |
+
elem_id="ch_nsfw_preview_dl_drop"
|
| 658 |
+
)
|
| 659 |
+
with gr.Row():
|
| 660 |
+
with gr.Column(scale=2):
|
| 661 |
+
check_models_new_version_btn = gr.Button(
|
| 662 |
+
value="Check New Version from Civitai",
|
| 663 |
+
variant="primary"
|
| 664 |
+
)
|
| 665 |
+
|
| 666 |
+
with gr.Row():
|
| 667 |
+
with gr.Column():
|
| 668 |
+
dl_new_version_log_md = gr.Markdown()
|
| 669 |
+
check_models_new_version_log_md = gr.HTML(
|
| 670 |
+
"It takes time, just wait. Check console log for details"
|
| 671 |
+
)
|
| 672 |
+
|
| 673 |
+
# ====events====
|
| 674 |
+
check_models_new_version_btn.click(
|
| 675 |
+
model_action_civitai.check_models_new_version_to_md,
|
| 676 |
+
inputs=model_types_ckbg,
|
| 677 |
+
outputs=check_models_new_version_log_md
|
| 678 |
+
)
|
| 679 |
+
|
| 680 |
+
js_dl_model_new_version_btn = gr.Button(
|
| 681 |
+
value="Download Model's new version",
|
| 682 |
+
visible=False,
|
| 683 |
+
elem_id="ch_js_dl_model_new_version_btn"
|
| 684 |
+
)
|
| 685 |
+
|
| 686 |
+
js_dl_model_new_version_btn.click(
|
| 687 |
+
js_action_civitai.dl_model_new_version,
|
| 688 |
+
inputs=[
|
| 689 |
+
js_msg_txtbox,
|
| 690 |
+
nsfw_preview_update_drop
|
| 691 |
+
],
|
| 692 |
+
outputs=dl_new_version_log_md
|
| 693 |
+
)
|
extensions/Stable-Diffusion-Webui-Civitai-Helper/style.css
CHANGED
|
@@ -5,7 +5,7 @@ blockquote ul {
|
|
| 5 |
|
| 6 |
blockquote ol {
|
| 7 |
list-style:decimal;
|
| 8 |
-
margin:4px 40px;
|
| 9 |
}
|
| 10 |
|
| 11 |
.block.padded.ch_box {
|
|
@@ -16,3 +16,110 @@ blockquote ol {
|
|
| 16 |
padding: 10px 0 !important;
|
| 17 |
}
|
| 18 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5 |
|
| 6 |
blockquote ol {
|
| 7 |
list-style:decimal;
|
| 8 |
+
margin: 4px 40px;
|
| 9 |
}
|
| 10 |
|
| 11 |
.block.padded.ch_box {
|
|
|
|
| 16 |
padding: 10px 0 !important;
|
| 17 |
}
|
| 18 |
|
| 19 |
+
#tab_civitai_helper .ch_vmargin {
|
| 20 |
+
margin-top: 10px;
|
| 21 |
+
margin-bottom: 10px;
|
| 22 |
+
}
|
| 23 |
+
|
| 24 |
+
#tab_civitai_helper .extra-network-cards {
|
| 25 |
+
min-height: 0;
|
| 26 |
+
max-height: 50vh;
|
| 27 |
+
}
|
| 28 |
+
|
| 29 |
+
.card-button {
|
| 30 |
+
margin: 0px 2px;
|
| 31 |
+
padding: 2px;
|
| 32 |
+
font-size: 150%;
|
| 33 |
+
}
|
| 34 |
+
|
| 35 |
+
#tab_civitai_helper .justify-bottom {
|
| 36 |
+
justify-content: flex-end;
|
| 37 |
+
}
|
| 38 |
+
|
| 39 |
+
#tab_civitai_helper .ch_grid div {
|
| 40 |
+
flex-basis: 30%;
|
| 41 |
+
}
|
| 42 |
+
|
| 43 |
+
#tab_civitai_helper .flex-center {
|
| 44 |
+
justify-content: center;
|
| 45 |
+
align-content: center;
|
| 46 |
+
}
|
| 47 |
+
@media only screen and (min-width: 600px) {
|
| 48 |
+
#ch_dl_preview_img {
|
| 49 |
+
max-width: 512px;
|
| 50 |
+
min-width: 512px;
|
| 51 |
+
max-height: 768px;
|
| 52 |
+
}
|
| 53 |
+
}
|
| 54 |
+
|
| 55 |
+
/* SD WebUI has not line height limit for model names.
|
| 56 |
+
* This can result in the model name filling the entire card,
|
| 57 |
+
* hiding our buttons. This is honestly a WebUI issue more
|
| 58 |
+
* than an issue on this end, but we can fix it anyways, so
|
| 59 |
+
* why not?
|
| 60 |
+
*/
|
| 61 |
+
.extra-network-cards .card .name:not(:hover) {
|
| 62 |
+
display: block;
|
| 63 |
+
overflow: hidden;
|
| 64 |
+
max-height: 3em;
|
| 65 |
+
}
|
| 66 |
+
|
| 67 |
+
.extra-network-thumbs .card-button {
|
| 68 |
+
font-size: 100%;
|
| 69 |
+
display: inline;
|
| 70 |
+
position: static;
|
| 71 |
+
background-image: none;
|
| 72 |
+
background: rgba(0, 0, 0, 0.8);
|
| 73 |
+
}
|
| 74 |
+
|
| 75 |
+
.duplicate_model {
|
| 76 |
+
display: flex;
|
| 77 |
+
flex-flow: row wrap;
|
| 78 |
+
justify-content: flex-start;
|
| 79 |
+
align-items: flex-start;
|
| 80 |
+
gap: 10px;
|
| 81 |
+
}
|
| 82 |
+
|
| 83 |
+
#ch_path_el {
|
| 84 |
+
position: fixed;
|
| 85 |
+
border: var(--input-border-width) solid var(--input-border-color);
|
| 86 |
+
border-radius: var(--input-radius);
|
| 87 |
+
background: var(--input-background-fill);
|
| 88 |
+
padding: var(--input-padding);
|
| 89 |
+
color: var(--body-text-color);
|
| 90 |
+
font-weight: var(--input-text-weight);
|
| 91 |
+
font-size: var(--input-text-size);
|
| 92 |
+
line-height: var(--line-sm);
|
| 93 |
+
box-shadow: var(--input-shadow);
|
| 94 |
+
font-family:var(--font);
|
| 95 |
+
pointer-events: none;
|
| 96 |
+
}
|
| 97 |
+
|
| 98 |
+
/* Lobe theme */
|
| 99 |
+
.actions .additional > a {
|
| 100 |
+
display: none;
|
| 101 |
+
}
|
| 102 |
+
|
| 103 |
+
/*
|
| 104 |
+
* Gross, ugly, and I hate it, but the only way to override an
|
| 105 |
+
* !important is with a more granular !important.
|
| 106 |
+
*/
|
| 107 |
+
.acss-culdhq .extra-networks .actions .additional > ul a.card-button {
|
| 108 |
+
margin: 0px 2px !important;
|
| 109 |
+
padding: 2px !important;
|
| 110 |
+
font-size: 150% !important;
|
| 111 |
+
}
|
| 112 |
+
|
| 113 |
+
/* Work around a display bug in Lobe that causes lowers opacity to 0.2 */
|
| 114 |
+
#txt2img_extra_tabs [style*="_cards_html"] .pending,
|
| 115 |
+
#img2img_extra_tabs [style*="_cards_html"] .pending {
|
| 116 |
+
opacity: 1;
|
| 117 |
+
}
|
| 118 |
+
|
| 119 |
+
/* Work around a display bug in Lobe that causes an infinite counter */
|
| 120 |
+
#txt2img_extra_tabs [id*="_cards_html"] .progress-text ,
|
| 121 |
+
#img2img_extra_tabs [id*="_cards_html"] .progress-text {
|
| 122 |
+
display: none;
|
| 123 |
+
}
|
| 124 |
+
|
| 125 |
+
/* end of Lobe theme */
|
extensions/sd-webui-infinite-image-browsing/.env
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
IIB_SECRET_KEY=
|
| 2 |
+
IIB_ACCESS_CONTROL=disable
|
| 3 |
+
IIB_ACCESS_CONTROL_PERMISSION=read-write
|
extensions/sd-webui-infinite-image-browsing/.gitignore
DELETED
|
@@ -1,17 +0,0 @@
|
|
| 1 |
-
|
| 2 |
-
*.log
|
| 3 |
-
__pycache__
|
| 4 |
-
iib.db
|
| 5 |
-
tags-translate.csv
|
| 6 |
-
launch.sh
|
| 7 |
-
conf.json
|
| 8 |
-
iib.db-journal
|
| 9 |
-
.env
|
| 10 |
-
standalone.cmd
|
| 11 |
-
.vscode
|
| 12 |
-
build/**/*
|
| 13 |
-
dist/**/*
|
| 14 |
-
*.spec
|
| 15 |
-
out/**/*
|
| 16 |
-
venv/**/*
|
| 17 |
-
zip_temp/*.zip
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
forcePull.bat
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
@echo off
|
| 2 |
+
|
| 3 |
+
git fetch --all
|
| 4 |
+
git reset --hard origin/main
|
| 5 |
+
git pull
|
| 6 |
+
git repack -a -d --depth=250 --window=250
|
| 7 |
+
cd .git
|
| 8 |
+
rmdir /S /Q lfs
|
| 9 |
+
cd ..
|
| 10 |
+
|
| 11 |
+
pause
|