pull/63/head
butaixianran 2023-03-12 23:08:58 +08:00
parent 0056e47de0
commit 0cff65be7c
16 changed files with 731 additions and 185 deletions

View File

@ -3,23 +3,31 @@ Stable Diffusion Webui 扩展Civitai助手用于更轻松的管理和使用Ci
[Civitai Url](https://civitai.com/models/16768/civitai-helper-sd-webui-civitai-extension)
# 注意
在安装或更新新版本之后需要整个关闭SD Webui重新启动它。光是Reload UI对本插件不起作用。
# 功能
* 扫描所有模型从Civitai下载模型信息和预览图
* 通过civitai模型的页面url连接一个本地模型和civitai模型的信息
* 通过civitai模型页面url连接本地模型和civitai模型信息
* 通过Civitai模型页面url下载模型(含信息和预览图)到SD目录或子目录。
* 下载支持断点续传
* 批量检查本地模型在civitai上的新版本
* 直接下载新版本模型到SD模型目录内(含信息和预览图)
* 修改了内置的"Extra Network"模型卡片,每个卡片增加了如下功能按钮:
- 🖼: 修改文字"replace preview"为这个图标
- 🌐: 在新标签页打开这个模型的Civitai页面
- 💡: 一键添加这个模型的触发词到关键词输入框
- 🏷: 一键使用这个模型预览图所使用的关键词
* 以上额外功能按钮支持thumbnail模式
* 增加一直显示按钮的选项,以供触屏用户使用
# 安装
下载本项目为zip文件解压到`你的SD webui目录/extensions`下即可。
然后使用设置页面的"Reload UI"按钮重新加载UI
然后整个关闭SD Webui重新启动它。只是Reload UI不起作用。以后更新也是必须重启SD Webui
(不了解git的国内用户请勿通过SD webui的插件界面安装或升级。
它安装的时候是调用你本地的git命令行工具来下载不是通过浏览器下载。你的git工具没有配置过代理和SSL的话就会下载失败)
(如果用SD webui的插件界面安装请先给git配置代理。它不是通过浏览器下载是通过git下载。)
# 使用方法
@ -67,12 +75,19 @@ Stable Diffusion Webui 扩展Civitai助手用于更轻松的管理和使用Ci
每次当Extra Network刷新他都会删除掉额外的修改我们的按钮就会消失。这时你就需要点击`Refresh Civitai Helper`把这些功能添加回去。
## 根据URL获取1个模型信息
如果无法在civitai上找到你的模型的SHA256但你还是希望能把你的模型连接到一个civitai模型你可以在本扩展页面从列表中选择你的模型并提供一个civitai模型页面的url。
### 小图模式
以上功能按钮支持小图模式但受制于SD Webui的CSS问题目前只能要么一直显示要么一直不显示不能鼠标滑过才显示。
![](img/thumb_mode.jpg)
点击按钮之后扩展就会下载那个civitai模型的信息作为你这个本地模型的信息使用。
## 下载
通过Civitai模型页面Url下载模型要3个步骤
* 填入url点击按钮获取模型信息
* 扩展会自动填入模型名称和类型,你需要选择下载的子目录和模型版本。
* 点击下载
![](img/download_model.jpg)
![](img/get_one_model_info.jpg)
下载过程会显示在命令行界面带个进度条。
支持断点续传,无畏大文件。
## 批量检查模型新版本
@ -85,10 +100,30 @@ Stable Diffusion Webui 扩展Civitai助手用于更轻松的管理和使用Ci
**检查完毕之后**就会如下图在UI上显示所有找到的新版本的信息。
每个模型新版本都有2个链接。第一个是这个模型的网页。第二个是这个新版本的下载地址。
每个模型新版本都有3个链接。
* 第一个是这个模型的网页。
* 第二个是这个新版本的下载地址。
* 第三个是个按钮在python端直接下载新版本到模型目录内。
这种方式下载,下载详情显示在"Download Model"的区域和命令行窗口中。一次一个任务,不支持多任务。
![](img/check_model_new_version_output.jpg)
## 根据URL获取模型信息
如果无法在civitai上找到你的模型的SHA256但你还是希望能把你的模型连接到一个civitai模型你可以在本扩展页面从列表中选择你的模型并提供一个civitai模型页面的url。
点击按钮之后扩展就会下载那个civitai模型的信息作为你这个本地模型的信息使用。
![](img/get_one_model_info.jpg)
## 其他设置
**保存设置按钮, 会保存扫描模型区域,以及其他设置 这两个区域的选项**
* "一直显示按钮" 是为了方便触屏。
* "小图模式显示功能按钮" 会开关功能按钮在小图模式的显示
![](img/other_setting.jpg)
## 预览图
Extra Network支持两种预览图命名`model_name.png` 和 `model_name.preview.png`。其中,`model_name.png`优先级较高。
@ -116,12 +151,18 @@ civitai不是每个图片都有关键词一个模型中也不是所有预
## Civitai本身挂掉
## Civitai API失效
Civitai在面临类似DDos的问题的时候会把整个网站立刻置于Cloudflare验证机制的保护下包括API的接口。于是API请求就会被跳转到Cloudflare的真人验证页面而无法获取任何信息。
这种情况请耐心等待Civitai恢复之后使用需要连接Civitai的功能。根据以往经验每次Civitai这样折腾一轮要6-8小时。
这种情况请耐心等待Civitai恢复之后再连接Civitai。根据以往经验每次Civitai这样折腾一轮要6-8小时。
## 新特性
从v1.5开始v1.x不再接受任何新特性。所有新特性进入2.x。
2.x专注于自定义模型信息并可能改名为"Model Info Helper"。因为不再是专注Civitai了。
从v1.5开始。v1.x进入维护阶段。
Enjoy!

View File

@ -2,7 +2,7 @@
[中文](README.cn.md)
# Notice
After updating to new version, you need to shutdown SD webui and re-launch. Just Reload UI won't work!
After install or update to new version, you need to shutdown SD webui and re-launch. Just Reload UI won't work!
# Stable-Diffusion-Webui-Civitai-Helper
Stable Diffusion Webui Extension for Civitai, to handle your models much more easily.
@ -12,12 +12,17 @@ Civitai: [Civitai Url](https://civitai.com/models/16768/civitai-helper-sd-webui-
# Features
* Scans all models to download model information and preview images from Civitai.
* Link 1 model to a civitai model by civitai model's url
* Checking all your local model's new version from civitai
* Download a model(with info+preview) by Civitai Url into SD's model folder or subfolder.
* Downloading can resume from break-point, which is good for large file.
* Checking all your local model's new version from Civitai
* Download a new version directly into SD model folder (with info+preview)
* Modified Built-in "Extra Network" cards, to add the following buttons on each card:
- 🖼: Modified "replace preview" text into this icon
- 🌐: Open this model's Civitai url in a new tab
- 💡: Add this model's trigger words to prompt
- 🏷: Use this model's preview image's prompt
* Also support thumbnail mode of Extra Network
* Option to always show addtional buttons, so now they work with touch screen.
# Install
@ -26,7 +31,7 @@ Copy this project's url into it, click install.
Alternatively, download this project as a zip file, and unzip it to `Your SD webui folder/extensions`.
Then reload UI with "Reload UI" Button in Setting tab.
Then shutdown SD Webui and Relaunch it. Just "Reload UI" won't work for this extension.
Done.
@ -74,13 +79,20 @@ Move your mouse on to the bottom of a model card. It will show 4 icon buttons:
![](img/refresh_ch.jpg)
Everytime after Extra Network tab refreshed, it will remove all these additional buttons. So, you need to click `Refresh Civitai Helper` button to bring them back.
## Get 1 Model Info By Url
If a model's SHA256 can not be found in civitai, but you still want to link it to a civitai model. You can choose this model from list, then offer a civitai model page's url you want to link.
### Thumbnail Mode
Additional buttons work on thumbnail too, but due to SD webui's CSS issue, for now, they must be always displayed on thumbnail or don't display at all.
![](img/thumb_mode.jpg)
After clicking button, extension will download that civitai model's model info for this local model file you picked.
![](img/get_one_model_info.jpg)
## Download
To download a model by Civitai Model Page's Url, you need 3 steps:
* Fill url, click button to get model info
* It will show model name and type automatically. Just choose sub-folder and model version
* Click download.
![](img/download_model.jpg)
Detail will be displayed on console log, with a progress bar.
Downloading can resume from break-point, so no fear for large file.
## Checking Model's New Version
You can checking your local model's new version from civitai by model types. You can select multiple model types.
@ -92,11 +104,30 @@ This is to protect Civitai from facing issue like DDos from this extension. Some
**After checking process done**, it will display all new version's information on UI.
There are 2 urls for each new version. First one is model's page. Second one is this version's download url.
There are 3 urls for each new version.
* First one is model's page.
* Second one is new version's download url.
* Third one is a button to download it into your SD's model folder with python.
With this one, output information is on "Download Model" section's log and console log. One task at a time.
![](img/check_model_new_version_output.jpg)
## Get Model Info By Url
If a model's SHA256 can not be found in civitai, but you still want to link it to a civitai model. You can choose this model from list, then offer a civitai model page's url you want to link.
After clicking button, extension will download that civitai model's model info for this local model file you picked.
![](img/get_one_model_info.jpg)
## Other Setting
**The Save Setting button, will save both "Scan Model"'s setting and other setting.**
* "Always Display Button" is good for touch screen.
* "Show Buttons on Thumb Mode" will turn on/off additional Buttons on thumbnail.
![](img/other_setting.jpg)
## Preview Image
Extra network uses both `model_file.png` and `model_file.preview.png` as preview image. But `model_file.png` has higher priority, because it is created by yourself.
@ -108,31 +139,39 @@ When you click the button "Use prompt from preview image", it does not use the p
On civitai, a model's preview images may not has prompt. This extension will check this model's all civitai preview images' information and use the first one has prompt in it.
## SHA256
To create a file SHA256, it need to read the whole file to generate a hash code. It gonna be slow for big files.
To create a file SHA256, it need to read the whole file to generate a hash code. It gonna be slow for large files.
Default, it uses a Memory Optimized SHA256 which won't stuck your system.(It is the only choice in latest version). So, do not uncheck it if you want to use your computer when scanning.
Also, extension uses Memory Optimized SHA256, which won't stuck your system and works with colab.
There are 2 cases this hash code can not find the model on civitai:
* Some old models, which do not have SHA256 code on civitai.
* The model's owner changed file on civitai, but does not change version number and description. So, the file on civitai is actually not the one on your manchine.
* The model's owner changed file on civitai, but does not change version name and description. So, the file on civitai is actually not the one on your manchine.
In these cases, you can always link a model to civitai by filling its URL in this extension.
## Civitai down
## Civitai API Fail
When Civitai is facing some issue like DDos, it gonna put civitai under Cloudflare's protection, which gonna re-direct our API request to a real human checking page. Then this extension can not get any information back.
In that case, juse wait for civitai's recovering. It could take 6-8 hours.
## Feature Request
No new feature for v1.x after v1.5. All new feature will go to 2.x.
2.x will focus on custom model information and may change name to "Model Info Helper", because it is not just focus on Civitai anymore.
From v1.5, v1.x goes into maintenance phase.
Enjoy!
# Change Log
## v1.5
## v1.5.0
* Download a model by Civitai model page's url
* Resume downloading from break-point
* Download new version into SD Webui's model folder
* Addtional button now works on thumbnail mode
* Add option to always show addtion button, for touch screen.
* Download a model by model page's url into SD webui's model folder
* Display checking new version's result as gallery and download new version into SD Webui's model folder
* Option to always show addtion button, for touch screen.
## v1.4.2
* ignore .vae file in model folder when scanning

Binary file not shown.

Before

Width:  |  Height:  |  Size: 110 KiB

After

Width:  |  Height:  |  Size: 116 KiB

BIN
img/download_model.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

BIN
img/other_setting.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

BIN
img/thumb_mode.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

View File

@ -1,5 +1,19 @@
"use strict";
// send msg to python side by filling a hidden text box
// then will click a button to trigger an action
// msg is an object, not a string, will be stringify in this function
function send_ch_py_msg(msg){
console.log("run send_ch_py_msg")
let js_msg_txtbox = gradioApp().querySelector("#ch_js_msg_txtbox textarea");
if (js_msg_txtbox && msg) {
// fill to msg box
js_msg_txtbox.value = JSON.stringify(msg);
js_msg_txtbox.dispatchEvent(new Event("input"));
}
}
// get msg from python side from a hidden textbox
// normally this is an old msg, need to wait for a new msg
function get_ch_py_msg(){
@ -17,9 +31,8 @@ function get_ch_py_msg(){
// get msg from python side from a hidden textbox
// if textbox's value is different from old value then it will consider it is a new msg
// it will try once in every sencond, until it reach the max try times
const get_new_ch_py_msg = (old_value, max_count=3) => new Promise((resolve, reject) => {
const get_new_ch_py_msg = (max_count=3) => new Promise((resolve, reject) => {
console.log("run get_new_ch_py_msg")
let count = 0;
@ -35,15 +48,23 @@ const get_new_ch_py_msg = (old_value, max_count=3) => new Promise((resolve, reje
console.log(py_msg_txtbox.value)
new_msg = py_msg_txtbox.value
if (new_msg != old_value) {
if (new_msg != "") {
find_msg=true
}
}
if (find_msg) {
//clear msg in both sides
py_msg_txtbox.value = "";
py_msg_txtbox.dispatchEvent(new Event("input"));
resolve(new_msg);
clearInterval(interval);
} else if (count > max_count) {
//clear msg in both sides
py_msg_txtbox.value = "";
py_msg_txtbox.dispatchEvent(new Event("input"));
reject('');
clearInterval(interval);
}
@ -51,6 +72,9 @@ const get_new_ch_py_msg = (old_value, max_count=3) => new Promise((resolve, reje
}, 1000);
})
function getActivePrompt() {
const currentTab = get_uiCurrentTabContent();
switch (currentTab.id) {
@ -79,8 +103,10 @@ async function open_model_url(event, model_type, search_term){
console.log("start open_model_url");
//get hidden components of extension
let js_msg_txtbox = gradioApp().querySelector("#ch_js_msg_txtbox textarea");
let js_open_url_btn = gradioApp().getElementById("ch_js_open_url_btn");
if (!js_open_url_btn) {
return
}
//msg to python side
@ -100,11 +126,7 @@ async function open_model_url(event, model_type, search_term){
msg["neg_prompt"] = "";
// fill to msg box
js_msg_txtbox.value = JSON.stringify(msg);
js_msg_txtbox.dispatchEvent(new Event("input"));
//get old py msg
let py_msg = get_ch_py_msg();
send_ch_py_msg(msg)
//click hidden button
js_open_url_btn.click();
@ -114,7 +136,7 @@ async function open_model_url(event, model_type, search_term){
event.preventDefault()
//check response msg from python
let new_py_msg = await get_new_ch_py_msg("");
let new_py_msg = await get_new_ch_py_msg();
console.log("new_py_msg:");
console.log(new_py_msg);
@ -142,9 +164,10 @@ function add_trigger_words(event, model_type, search_term){
console.log("start add_trigger_words");
//get hidden components of extension
let js_msg_txtbox = gradioApp().querySelector("#ch_js_msg_txtbox textarea");
let js_add_trigger_words_btn = gradioApp().getElementById("ch_js_add_trigger_words_btn");
if (!js_add_trigger_words_btn) {
return
}
//msg to python side
@ -166,8 +189,7 @@ function add_trigger_words(event, model_type, search_term){
msg["prompt"] = prompt.value;
// fill to msg box
js_msg_txtbox.value = JSON.stringify(msg);
js_msg_txtbox.dispatchEvent(new Event("input"));
send_ch_py_msg(msg)
//click hidden button
js_add_trigger_words_btn.click();
@ -184,10 +206,10 @@ function use_preview_prompt(event, model_type, search_term){
console.log("start use_preview_prompt");
//get hidden components of extension
let js_msg_txtbox = gradioApp().querySelector("#ch_js_msg_txtbox textarea");
let js_use_preview_prompt_btn = gradioApp().getElementById("ch_js_use_preview_prompt_btn");
if (!js_use_preview_prompt_btn) {
return
}
//msg to python side
let msg = {
@ -211,8 +233,7 @@ function use_preview_prompt(event, model_type, search_term){
msg["neg_prompt"] = neg_prompt.value;
// fill to msg box
js_msg_txtbox.value = JSON.stringify(msg);
js_msg_txtbox.dispatchEvent(new Event("input"));
send_ch_py_msg(msg)
//click hidden button
js_use_preview_prompt_btn.click();
@ -225,6 +246,51 @@ function use_preview_prompt(event, model_type, search_term){
}
// download model's new version into SD at python side
function ch_dl_model_new_version(event, model_path, version_id, download_url){
console.log("start ch_dl_model_new_version");
// must confirm before downloading
let dl_confirm = "\nConfirm to download.\n\nCheck Download Model Section's log and console log for detail.";
if (!confirm(dl_confirm)) {
return
}
//get hidden components of extension
let js_dl_model_new_version_btn = gradioApp().getElementById("ch_js_dl_model_new_version_btn");
if (!js_dl_model_new_version_btn) {
return
}
//msg to python side
let msg = {
"action": "",
"model_path": "",
"version_id": "",
"download_url": "",
}
msg["action"] = "dl_model_new_version";
msg["model_path"] = model_path;
msg["version_id"] = version_id;
msg["download_url"] = download_url;
// fill to msg box
send_ch_py_msg(msg)
//click hidden button
js_dl_model_new_version_btn.click();
console.log("end dl_model_new_version");
event.stopPropagation()
event.preventDefault()
}
onUiLoaded(() => {

View File

@ -35,7 +35,6 @@ def on_ui_tabs():
# init
# get prompt textarea
# UI structure
# check modules/ui.py, search for txt2img_paste_fields
# Negative prompt is the second element
txt2img_prompt = modules.ui.txt2img_paste_fields[0][0]
@ -49,6 +48,18 @@ def on_ui_tabs():
return model_name_drop.update(choices=names)
def get_model_info_by_url(url):
r = model_action_civitai.get_model_info_by_url(url)
model_info = {}
model_name = ""
model_type = ""
subfolders = []
version_strs = []
if r:
model_info, model_name, model_type, subfolders, version_strs = r
return [model_info, model_name, model_type, dl_subfolder_drop.update(choices=subfolders), dl_version_drop.update(choices=version_strs)]
# ====UI====
# with gr.Blocks(analytics_enabled=False) as civitai_helper:
@ -64,6 +75,10 @@ def on_ui_tabs():
model_types = list(model.folders.keys())
no_info_model_names = civitai.get_model_names_by_input("ckp", False)
# session data
dl_model_info = gr.State({})
# with gr.Tab("Model"):
with gr.Box():
@ -84,13 +99,30 @@ def on_ui_tabs():
gr.Markdown("### Get Civitai Model Info by Model Page URL")
with gr.Row():
model_type_drop = gr.Dropdown(choices=model_types, label="Model Type", value="ckp", multiselect=False)
empty_info_only_ckb = gr.Checkbox(label="Only Show Models have no Info file", value=False, elem_id="cn_empty_info_only_ckb")
empty_info_only_ckb = gr.Checkbox(label="Only Show Models have no Info", value=False, elem_id="cn_empty_info_only_ckb")
model_name_drop = gr.Dropdown(choices=no_info_model_names, label="Model", value="ckp", multiselect=False)
model_url_or_id = gr.Textbox(label="Civitai URL or Model ID", lines=1, value="")
model_url_or_id_txtbox = gr.Textbox(label="Civitai URL or Model ID", lines=1, value="")
get_civitai_model_info_by_id_btn = gr.Button(value="Get Model Info from Civitai", variant="primary")
get_model_by_id_log_md = gr.Markdown("")
with gr.Box():
with gr.Column():
gr.Markdown("### Download Model")
with gr.Row():
dl_model_url_or_id_txtbox = gr.Textbox(label="Civitai URL or Model ID", lines=1, value="")
dl_model_info_btn = gr.Button(value="1. Get Model Info by Civitai Url", variant="primary")
gr.Markdown(value="2. Pick Subfolder and Model Version")
with gr.Row():
dl_model_name_txtbox = gr.Textbox(label="Model Name", interactive=False, lines=1, value="")
dl_model_type_txtbox = gr.Textbox(label="Model Type", interactive=False, lines=1, value="")
dl_subfolder_drop = gr.Dropdown(choices=[], label="Sub-folder", value="", interactive=True, multiselect=False)
dl_version_drop = gr.Dropdown(choices=[], label="Model Version", value="", interactive=True, multiselect=False)
dl_civitai_model_by_id_btn = gr.Button(value="3. Download Model", variant="primary")
dl_log_md = gr.Markdown(value="Check Console log for Downloading Status")
with gr.Box():
with gr.Column():
gr.Markdown("### Check models' new version")
@ -98,19 +130,7 @@ def on_ui_tabs():
model_types_ckbg = gr.CheckboxGroup(choices=model_types, label="Model Types", value=["lora"])
check_models_new_version_btn = gr.Button(value="Check New Version from Civitai", variant="primary")
check_models_new_version_log_md = gr.Markdown("It takes time, just wait. Check console log for detail")
with gr.Box():
with gr.Column():
gr.Markdown("### Download Model")
with gr.Row():
dl_model_type_drop = gr.Dropdown(choices=model_types, label="Model Type", value="ckp", multiselect=False)
dl_subfolder_drop = gr.Dropdown(choices=model_types, label="Sub-folder", value="", multiselect=False)
dl_model_url_or_id = gr.Textbox(label="Civitai URL or Model ID", lines=1, value="")
dl_civitai_model_by_id_btn = gr.Button(value="Download Model from Civitai", variant="primary")
check_models_new_version_log_md = gr.HTML("It takes time, just wait. Check console log for detail")
with gr.Box():
with gr.Column():
@ -120,8 +140,8 @@ def on_ui_tabs():
always_display_ckb = gr.Checkbox(label="Always Display Buttons", value=always_display, elem_id="ch_always_display_ckb")
show_btn_on_thumb_ckb = gr.Checkbox(label="Show Button On Thumb Mode", value=show_btn_on_thumb, elem_id="ch_show_btn_on_thumb_ckb")
save_setting_btn = gr.Button(value="Save Setting", elem_id="ch_save_setting_btn")
general_log_md = gr.Markdown(value="", elem_id="ch_general_log_md")
save_setting_btn = gr.Button(value="Save Setting")
general_log_md = gr.Markdown(value="")
# ====Footer====
@ -130,29 +150,37 @@ def on_ui_tabs():
# ====hidden component for js, not in any tab====
js_msg_txtbox = gr.Textbox(label="Request Msg From Js", visible=False, lines=1, value="", elem_id="ch_js_msg_txtbox")
py_msg_txtbox = gr.Textbox(label="Response Msg From Python", visible=False, lines=1, value="", elem_id="ch_py_msg_txtbox")
js_open_url_btn = gr.Button(value="Open Model Url", visible=False, elem_id="ch_js_open_url_btn")
js_add_trigger_words_btn = gr.Button(value="Add Trigger Words", visible=False, elem_id="ch_js_add_trigger_words_btn")
js_use_preview_prompt_btn = gr.Button(value="Use Prompt from Preview Image", visible=False, elem_id="ch_js_use_preview_prompt_btn")
js_dl_model_new_version_btn = gr.Button(value="Download Model's new version", visible=False, elem_id="ch_js_dl_model_new_version_btn")
# ====events====
# Model
# Scan Models for Civitai
scan_model_civitai_btn.click(model_action_civitai.scan_model, inputs=[max_size_preview_ckb, skip_nsfw_preview_ckb], outputs=scan_model_log_md)
# Get Civitai Model Info by Model Page URL
model_type_drop.change(get_model_names_by_input, inputs=[model_type_drop, empty_info_only_ckb], outputs=model_name_drop)
empty_info_only_ckb.change(get_model_names_by_input, inputs=[model_type_drop, empty_info_only_ckb], outputs=model_name_drop)
get_civitai_model_info_by_id_btn.click(model_action_civitai.get_model_info_by_input, inputs=[model_type_drop, model_name_drop, model_url_or_id, max_size_preview_ckb, skip_nsfw_preview_ckb], outputs=get_model_by_id_log_md)
get_civitai_model_info_by_id_btn.click(model_action_civitai.get_model_info_by_input, inputs=[model_type_drop, model_name_drop, model_url_or_id_txtbox, max_size_preview_ckb, skip_nsfw_preview_ckb], outputs=get_model_by_id_log_md)
# Download Model
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])
dl_civitai_model_by_id_btn.click(model_action_civitai.dl_model_by_input, inputs=[dl_model_info, dl_model_type_txtbox, dl_subfolder_drop, dl_version_drop, max_size_preview_ckb, skip_nsfw_preview_ckb], outputs=dl_log_md)
# Check models' new version
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)
# General
# Other Setting
save_setting_btn.click(setting.save_from_input, inputs=[max_size_preview_ckb, skip_nsfw_preview_ckb, open_url_with_js_ckb, always_display_ckb, show_btn_on_thumb_ckb], outputs=general_log_md)
# js action
js_open_url_btn.click(js_action_civitai.open_model_url, inputs=[js_msg_txtbox, open_url_with_js_ckb], outputs=py_msg_txtbox)
js_add_trigger_words_btn.click(js_action_civitai.add_trigger_words, inputs=[js_msg_txtbox], outputs=[txt2img_prompt, img2img_prompt])
js_use_preview_prompt_btn.click(js_action_civitai.use_preview_image_prompt, inputs=[js_msg_txtbox], outputs=[txt2img_prompt, txt2img_neg_prompt, img2img_prompt, img2img_neg_prompt])
js_dl_model_new_version_btn.click(js_action_civitai.dl_model_new_version, inputs=[js_msg_txtbox, max_size_preview_ckb, skip_nsfw_preview_ckb], outputs=dl_log_md)
# the third parameter is the element id on html, with a "tab_" as prefix
return (civitai_helper , "Civitai Helper", "civitai_helper"),

View File

@ -18,6 +18,14 @@ url_dict = {
"hash": "https://civitai.com/api/v1/model-versions/by-hash/"
}
model_type_dict = {
"Checkpoint": "ckp",
"TextualInversion": "ti",
"Hypernetwork": "hyper",
"LORA": "lora",
}
# get image with full size
# width is in number, not string
# return: url str
@ -107,7 +115,7 @@ def get_version_info_by_version_id(id:str) -> dict:
util.printD("id is empty")
return
r = requests.get(url_dict["modelVersionId"]+id)
r = requests.get(url_dict["modelVersionId"]+str(id))
if not r.ok:
if r.status_code == 404:
# this is not a civitai model
@ -303,7 +311,7 @@ def get_model_id_from_url(url:str) -> str:
# get preview image by model path
# image will be saved to file, so no return
def get_preview_image_by_model_path(model_path:str, max_size_preview, skip_nsfw_preview) -> str:
def get_preview_image_by_model_path(model_path:str, max_size_preview, skip_nsfw_preview):
if not model_path:
util.printD("model_path is empty")
return
@ -599,3 +607,4 @@ def check_models_new_version_by_model_types(model_types:list, delay:float=1) ->
return new_versions

View File

@ -1,61 +0,0 @@
# -*- coding: UTF-8 -*-
import sys
import requests
import os
import util
dl_ext = ".downloading"
# disable ssl warning info
requests.packages.urllib3.disable_warnings()
def download(url, file_path, size):
# use a temp file for downloading
base, ext = os.path.splitext(file_path)
dl_file_path = os.path.join(base, dl_ext)
total_size = 0
if size:
total_size = size
else:
# get file size
rhead = requests.get(url, stream=True, verify=False)
total_size = int(rhead.headers['Content-Length'])
util.printD(f"File size: {total_size}")
util.printD(f"Downloading to temp file: {dl_file_path}")
# check if downloading file is exsited
downloaded_size = 0
if os.path.exists(dl_file_path):
downloaded_size = os.path.getsize(dl_file_path)
util.printD(f"Downloaded size: {downloaded_size}")
# get header range
headers = {'Range': 'bytes=%d-' % downloaded_size}
# download with header
r = requests.get(url, stream=True, verify=False, headers=headers)
# write to file
with open(file_path, "ab") as f:
for chunk in r.iter_content(chunk_size=1024):
if chunk:
downloaded_size += len(chunk)
f.write(chunk)
# force to write to disk
f.flush()
# progress
progress = int(50 * downloaded_size / total_size)
sys.stdout.write("\r[%s%s] %d%%" % ('' * progress, ' ' * (50 - progress), 100 * downloaded_size / total_size))
sys.stdout.flush()
print()
# rename file
os.rename(dl_file_path, file_path)
util.printD(f"File Downloaded to: {file_path}")

102
scripts/lib/downloader.py Normal file
View File

@ -0,0 +1,102 @@
# -*- coding: UTF-8 -*-
import sys
import requests
import os
from . import util
dl_ext = ".downloading"
# disable ssl warning info
requests.packages.urllib3.disable_warnings()
# output is downloaded file path
def dl(url, folder, filename, filepath):
util.printD("Start downloading from: " + url)
# get file_path
file_path = ""
if filepath:
file_path = filepath
else:
# if file_path is not in parameter, then folder must be in parameter
if not folder:
util.printD("folder is none")
return
if not os.path.isdir(folder):
util.printD("folder does not exist: "+folder)
return
if filename:
file_path = os.path.join(folder, filename)
# first request for header
rh = requests.get(url, stream=True, verify=False)
# get file size
total_size = 0
total_size = int(rh.headers['Content-Length'])
util.printD(f"File size: {total_size}")
# if file_path is empty, need to get file name from download url's header
if not file_path:
filename = ""
if "Content-Disposition" in rh.headers.keys():
cd = rh.headers["Content-Disposition"]
# Extract the filename from the header
# content of a CD: "attachment;filename=FileName.txt"
# in case "" is in CD filename's start and end, need to strip them out
filename = cd.split("=")[1].strip('"')
if not filename:
util.printD("Fail to get file name from Content-Disposition: " + cd)
return
if not filename:
util.printD("Can not get file name from download url's header")
return
# with folder and filename, now we have the full file path
file_path = os.path.join(folder, filename)
util.printD("Target file path: " + file_path)
# use a temp file for downloading
base, ext = os.path.splitext(file_path)
dl_file_path = base+dl_ext
util.printD(f"Downloading to temp file: {dl_file_path}")
# check if downloading file is exsited
downloaded_size = 0
if os.path.exists(dl_file_path):
downloaded_size = os.path.getsize(dl_file_path)
util.printD(f"Downloaded size: {downloaded_size}")
# create header range
headers = {'Range': 'bytes=%d-' % downloaded_size}
# download with header
r = requests.get(url, stream=True, verify=False, headers=headers)
# write to file
with open(dl_file_path, "ab") as f:
for chunk in r.iter_content(chunk_size=1024):
if chunk:
downloaded_size += len(chunk)
f.write(chunk)
# force to write to disk
f.flush()
# progress
progress = int(50 * downloaded_size / total_size)
sys.stdout.write("\r[%s%s] %d%%" % ('' * progress, ' ' * (50 - progress), 100 * downloaded_size / total_size))
sys.stdout.flush()
print()
# rename file
os.rename(dl_file_path, file_path)
util.printD(f"File Downloaded to: {file_path}")
return file_path

View File

@ -8,7 +8,7 @@ from . import util
from . import model
from . import civitai
from . import msg_handler
from . import downloader
@ -24,7 +24,8 @@ def open_model_url(msg, open_url_with_js):
util.printD("Parsing js ms failed")
return
action, model_type, search_term, prompt, neg_prompt = result
model_type = result["model_type"]
search_term = result["search_term"]
model_info = civitai.load_model_info_by_search_term(model_type, search_term)
if not model_info:
@ -73,7 +74,9 @@ def add_trigger_words(msg):
util.printD("Parsing js ms failed")
return
action, model_type, search_term, prompt, neg_prompt = result
model_type = result["model_type"]
search_term = result["search_term"]
prompt = result["prompt"]
model_info = civitai.load_model_info_by_search_term(model_type, search_term)
@ -122,7 +125,10 @@ def use_preview_image_prompt(msg):
util.printD("Parsing js ms failed")
return
action, model_type, search_term, prompt, neg_prompt = result
model_type = result["model_type"]
search_term = result["search_term"]
prompt = result["prompt"]
neg_prompt = result["neg_prompt"]
model_info = civitai.load_model_info_by_search_term(model_type, search_term)
@ -170,3 +176,73 @@ def use_preview_image_prompt(msg):
return [preview_prompt, preview_neg_prompt, preview_prompt, preview_neg_prompt]
# download model's new verson by model path, version id and download url
# output is a md log
def dl_model_new_version(msg, max_size_preview, skip_nsfw_preview):
util.printD("Start dl_model_new_version")
output = ""
result = msg_handler.parse_js_msg(msg)
if not result:
output = "Parsing js ms failed"
util.printD(output)
return output
model_path = result["model_path"]
version_id = result["version_id"]
download_url = result["download_url"]
util.printD("model_path: " + model_path)
util.printD("version_id: " + str(version_id))
util.printD("download_url: " + download_url)
# check data
if not model_path:
output = "model_path is empty"
util.printD(output)
return output
if not version_id:
output = "version_id is empty"
util.printD(output)
return output
if not download_url:
output = "download_url is empty"
util.printD(output)
return output
if not os.path.isfile(model_path):
output = "model_path is not a file: "+ model_path
util.printD(output)
return output
# get model folder from model path
model_folder = os.path.dirname(model_path)
# download file
new_model_path = downloader.dl(download_url, model_folder, None, None)
if not new_model_path:
output = "Download failed, check console log for detail. Download url: " + download_url
util.printD(output)
return output
# get version info
version_info = civitai.get_version_info_by_version_id(version_id)
if not version_info:
output = "Model downloaded, but failed to get version info, check console log for detail. Model saved to: " + new_model_path
util.printD(output)
return output
# now write version info to file
base, ext = os.path.splitext(new_model_path)
info_file = base + civitai.suffix + model.info_ext
model.write_model_info(info_file, version_info)
# then, get preview image
civitai.get_preview_image_by_model_path(new_model_path, max_size_preview, skip_nsfw_preview)
output = "Done. Model downloaded to: " + new_model_path
util.printD(output)
return output

View File

@ -4,7 +4,7 @@ import os
from . import util
from . import model
from . import civitai
from . import downloader
# scan model to generate SHA256, then use this SHA256 to get model info from civitai
@ -139,24 +139,33 @@ def check_models_new_version_to_md(model_types:list) -> str:
model_path, model_id, model_name, new_verion_id, new_version_name, description, download_url, img_url = new_version
# in md, each part is something like this:
# [model_name](model_url)
# [version_name](download_url)
# version description
url = civitai.url_dict["modelPage"]+str(model_id)
part = f'<b>Model: <a href="{url}" target="_blank">{model_name}</a></b> <br>'
part = part + f"File: {model_path} <br>"
part = f'<div style="font-size:20px;margin:6px 0px;"><b>Model: <a href="{url}" target="_blank"><u>{model_name}</u></a></b></div>'
part = part + f'<div style="font-size:16px">File: {model_path}</div>'
if download_url:
part = part + f'New Version: <a href="{download_url}" target="_blank">{new_version_name}</a>'
# replace "\" to "/" in model_path for windows
model_path = model_path.replace('\\', '\\\\')
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>'
# add js function to download new version into SD webui by python
part = part + " "
# in embed HTML, onclick= will also follow a ", never a ', so have to write it as following
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>"
else:
part = part + f"New Version: {new_version_name}"
part = part + " <br>"
part = part + f'<div style="font-size:16px;margin:6px 0px;">New Version: {new_version_name}'
part = part + '</div>'
# description
if description:
part = part + description
part = part + '<blockquote style="font-size:16px;margin:6px 0px;">'+ description + '</blockquote><br>'
# preview image
if img_url:
part = part + f"![]({img_url}) <br>"
part = part + f"<img src='{img_url}'><br>"
output = output + part
@ -165,3 +174,233 @@ def check_models_new_version_to_md(model_types:list) -> str:
return output
# get model info by url
def get_model_info_by_url(model_url_or_id:str) -> tuple:
util.printD("Getting model info by: " + model_url_or_id)
# parse model id
model_id = civitai.get_model_id_from_url(model_url_or_id)
if not model_id:
util.printD("failed to parse model id from url or id")
return
model_info = civitai.get_model_info_by_id(model_id)
if not model_info:
util.printD("failed to get model info from url or id")
return
# parse model type, model name, subfolder, version from this model info
# get model type
if "type" not in model_info.keys():
util.printD("model type is not in model_info")
return
civitai_model_type = model_info["type"]
if civitai_model_type not in civitai.model_type_dict.keys():
util.printD("This model type is not supported:"+civitai_model_type)
return
model_type = civitai.model_type_dict[civitai_model_type]
# get model type
if "name" not in model_info.keys():
util.printD("model name is not in model_info")
return
model_name = model_info["name"]
if not model_name:
util.printD("model name is Empty")
model_name = ""
# get version list
if "modelVersions" not in model_info.keys():
util.printD("modelVersions is not in model_info")
return
modelVersions = model_info["modelVersions"]
if not modelVersions:
util.printD("modelVersions is Empty")
return
version_strs = []
for version in modelVersions:
# version name can not be used as id
# version id is not readable
# so , we use name_id as version string
version_str = version["name"]+"_"+str(version["id"])
version_strs.append(version_str)
# get folder by model type
folder = model.folders[model_type]
# get subfolders
subfolders = util.get_subfolders(folder)
if not subfolders:
subfolders = []
# add default root folder
subfolders.append("/")
util.printD("Get following info for downloading:")
util.printD(f"model_name:{model_name}")
util.printD(f"model_type:{model_type}")
util.printD(f"subfolders:{subfolders}")
util.printD(f"version_strs:{version_strs}")
return (model_info, model_name, model_type, subfolders, version_strs)
# get download url from model info by version string
# return - (version_id, download_url)
def get_id_and_dl_url_by_version_str(version_str:str, model_info:dict) -> tuple:
if not version_str:
util.printD("version_str is empty")
return
if not model_info:
util.printD("model_info is None")
return
# get version list
if "modelVersions" not in model_info.keys():
util.printD("modelVersions is not in model_info")
return
modelVersions = model_info["modelVersions"]
if not modelVersions:
util.printD("modelVersions is Empty")
return
# find version by version_str
version = None
for ver in modelVersions:
# version name can not be used as id
# version id is not readable
# so , we use name_id as version string
ver_str = ver["name"]+"_"+str(ver["id"])
if ver_str == version_str:
# find version
version = ver
if not version:
util.printD("can not find version by version string: " + version_str)
return
# get version id
if "id" not in version.keys():
util.printD("this version has no id")
return
version_id = version["id"]
if not version_id:
util.printD("version id is Empty")
return
# get download url
if "downloadUrl" not in version.keys():
util.printD("downloadUrl is not in this version")
return
downloadUrl = version["downloadUrl"]
if not downloadUrl:
util.printD("downloadUrl is Empty")
return
util.printD("Get Download Url: " + downloadUrl)
return (version_id, downloadUrl)
# download model from civitai by input
# output to markdown log
def dl_model_by_input(model_info:dict, model_type:str, subfolder_str:str, version_str:str, max_size_preview:bool, skip_nsfw_preview:bool) -> str:
output = ""
if not model_info:
output = "model_info is None"
util.printD(output)
return output
if not model_type:
output = "model_type is None"
util.printD(output)
return output
if not subfolder_str:
output = "subfolder string is None"
util.printD(output)
return output
if not version_str:
output = "version_str is None"
util.printD(output)
return output
# get model root folder
if model_type not in model.folders.keys():
output = "Unknow model type: "+model_type
util.printD(output)
return output
model_root_folder = model.folders[model_type]
# get subfolder
subfolder = ""
if subfolder_str == "/" or subfolder_str == "\\":
subfolder = ""
elif subfolder_str[:1] == "/" or subfolder_str[:1] == "\\":
subfolder = subfolder_str[1:]
else:
subfolder = subfolder_str
# get model folder for downloading
model_folder = os.path.join(model_root_folder, subfolder)
if not os.path.isdir(model_folder):
output = "Model folder is not a dir: "+ model_folder
util.printD(output)
return output
# get download url
r = get_id_and_dl_url_by_version_str(version_str, model_info)
if not r:
output = "Fail to get download url, check console log for detail"
util.printD(output)
return output
version_id, url = r
if not version_id:
output = "Fail to get version id, check console log for detail"
util.printD(output)
return output
if not url:
output = "Fail to get download url, check console log for detail"
util.printD(output)
return output
# download
filepath = downloader.dl(url, model_folder, None, None)
if not filepath:
output = "Downloading failed, check console log for detail"
util.printD(output)
return output
# get version info
version_info = civitai.get_version_info_by_version_id(version_id)
if not version_info:
output = "Model downloaded, but failed to get version info, check console log for detail. Model saved to: " + filepath
util.printD(output)
return output
# write version info to file
base, ext = os.path.splitext(filepath)
info_file = base + civitai.suffix + model.info_ext
model.write_model_info(info_file, version_info)
# then, get preview image
civitai.get_preview_image_by_model_path(filepath, max_size_preview, skip_nsfw_preview)
output = "Done. Model downloaded to: " + filepath
util.printD(output)
return output

View File

@ -4,63 +4,37 @@ import json
from . import util
# action list
js_actions = ("open_url", "add_trigger_words", "use_preview_prompt")
py_actions = ("open_url", "scan_log", "model_new_version")
js_actions = ("open_url", "add_trigger_words", "use_preview_prompt", "dl_model_new_version")
py_actions = ("open_url")
# handle request from javascript
# parameter: msg - msg from js as string in a hidden textbox
# return: (action, model_type, search_term, prompt, neg_prompt)
# return: dict for result
def parse_js_msg(msg):
util.printD("Start parse js msg")
msg_dict = json.loads(msg)
# in case client side run JSON.stringify twice
if (type(msg_dict) == str):
msg_dict = json.loads(msg_dict)
if "action" not in msg_dict.keys():
util.printD("Can not find action from js request")
return
if "model_type" not in msg_dict.keys():
util.printD("Can not find model type from js request")
return
if "search_term" not in msg_dict.keys():
util.printD("Can not find search_term from js request")
return
if "prompt" not in msg_dict.keys():
util.printD("Can not find prompt from js request")
return
if "neg_prompt" not in msg_dict.keys():
util.printD("Can not find neg_prompt from js request")
return
action = msg_dict["action"]
model_type = msg_dict["model_type"]
search_term = msg_dict["search_term"]
prompt = msg_dict["prompt"]
neg_prompt = msg_dict["neg_prompt"]
if not action:
util.printD("Action from js request is None")
return
if not model_type:
util.printD("model_type from js request is None")
return
if not search_term:
util.printD("search_term from js request is None")
return
if action not in js_actions:
util.printD("Unknow action: " + action)
return
util.printD("End parse js msg")
return (action, model_type, search_term, prompt, neg_prompt)
return msg_dict
# build python side msg for sending to js

View File

@ -1,9 +1,10 @@
# -*- coding: UTF-8 -*-
import os
import hashlib
import requests
import shutil
version = "1.4.3"
version = "1.5.0"
# print for debugging
def printD(msg):
@ -41,3 +42,26 @@ def download_file(url, path):
shutil.copyfileobj(r.raw, f)
printD("File downloaded to: " + path)
# get subfolder list
def get_subfolders(folder:str) -> list:
printD("Get subfolder for: " + folder)
if not folder:
printD("folder can not be None")
return
if not os.path.isdir(folder):
printD("path is not a folder")
return
prefix_len = len(folder)
subfolders = []
for root, dirs, files in os.walk(folder, followlinks=True):
for dir in dirs:
full_dir_path = os.path.join(root, dir)
# get subfolder path from it
subfolder = full_dir_path[prefix_len:]
subfolders.append(subfolder)
return subfolders

9
style.css Normal file
View File

@ -0,0 +1,9 @@
blockquote ul {
list-style:disc;
margin:4px 40px;
}
blockquote ol {
list-style:decimal;
margin:4px 40px;
}