Duskfallcrew
commited on
Upload 6 files
Browse filesThis is the untested 0.4 branch with new code.
- LICENSE +21 -0
- README.md +1 -6
- __init__.py +18 -0
- requirements.txt +2 -0
- scripts/hfbackup_script.py +187 -0
- scripts/ui-settings.py +37 -0
LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
MIT License
|
2 |
+
|
3 |
+
Copyright (c) 2024 The Duskfall Portal Crew
|
4 |
+
|
5 |
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6 |
+
of this software and associated documentation files (the "Software"), to deal
|
7 |
+
in the Software without restriction, including without limitation the rights
|
8 |
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9 |
+
copies of the Software, and to permit persons to whom the Software is
|
10 |
+
furnished to do so, subject to the following conditions:
|
11 |
+
|
12 |
+
The above copyright notice and this permission notice shall be included in all
|
13 |
+
copies or substantial portions of the Software.
|
14 |
+
|
15 |
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16 |
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17 |
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18 |
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19 |
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20 |
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21 |
+
SOFTWARE.
|
README.md
CHANGED
@@ -1,8 +1,3 @@
|
|
1 |
-
---
|
2 |
-
license: mit
|
3 |
-
language:
|
4 |
-
- en
|
5 |
-
---
|
6 |
# Hugging Face Backup Extension for Stable Diffusion WebUI
|
7 |
|
8 |
Welcome to our unique extension, designed to help you easily back up your valuable Stable Diffusion WebUI files to the Hugging Face Hub! This project is brought to you by the Duskfall Portal Crew, a diverse DID system passionate about creativity and AI.
|
@@ -96,4 +91,4 @@ We are the Duskfall Portal Crew, a DID system with over 300 alters, navigating l
|
|
96 |
|
97 |
#### Community Groups
|
98 |
|
99 |
-
* **Subreddit:** [Reddit](https://www.reddit.com/r/earthndusk/)
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
# Hugging Face Backup Extension for Stable Diffusion WebUI
|
2 |
|
3 |
Welcome to our unique extension, designed to help you easily back up your valuable Stable Diffusion WebUI files to the Hugging Face Hub! This project is brought to you by the Duskfall Portal Crew, a diverse DID system passionate about creativity and AI.
|
|
|
91 |
|
92 |
#### Community Groups
|
93 |
|
94 |
+
* **Subreddit:** [Reddit](https://www.reddit.com/r/earthndusk/)
|
__init__.py
ADDED
@@ -0,0 +1,18 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from modules import scripts
|
2 |
+
from scripts import hfbackup_script
|
3 |
+
|
4 |
+
class Script(scripts.Script):
|
5 |
+
def title(self):
|
6 |
+
return "Huggingface Backup"
|
7 |
+
|
8 |
+
def show(self, is_img2img):
|
9 |
+
return scripts.AlwaysVisible
|
10 |
+
|
11 |
+
def ui(self, is_img2img):
|
12 |
+
return hfbackup_script.on_ui(self)
|
13 |
+
|
14 |
+
def run(self, p, *args):
|
15 |
+
return hfbackup_script.on_run(self, p, *args)
|
16 |
+
|
17 |
+
def on_script_load(self):
|
18 |
+
return hfbackup_script.on_script_load(self)
|
requirements.txt
ADDED
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
1 |
+
huggingface_hub==4.30.2
|
2 |
+
gitpython
|
scripts/hfbackup_script.py
ADDED
@@ -0,0 +1,187 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os
|
2 |
+
import time
|
3 |
+
import datetime
|
4 |
+
import threading
|
5 |
+
import gradio as gr
|
6 |
+
import subprocess
|
7 |
+
import logging
|
8 |
+
from modules import script_callbacks, shared
|
9 |
+
from git import Repo
|
10 |
+
import shutil
|
11 |
+
|
12 |
+
# Constants
|
13 |
+
REPO_NAME = 'sd-webui-backups'
|
14 |
+
BACKUP_INTERVAL = 3600 # 1 hour in seconds
|
15 |
+
HF_TOKEN_KEY = 'hf_token'
|
16 |
+
BACKUP_PATHS_KEY = 'backup_paths'
|
17 |
+
SD_PATH_KEY = 'sd_path'
|
18 |
+
HF_USER_KEY = 'hf_user'
|
19 |
+
DEFAULT_BACKUP_PATHS = ['models/Stable-diffusion', 'models/VAE', 'embeddings', 'loras']
|
20 |
+
|
21 |
+
# --- Logging Setup ---
|
22 |
+
logging.basicConfig(level=logging.INFO,
|
23 |
+
format='%(asctime)s - %(levelname)s - %(message)s',
|
24 |
+
handlers=[
|
25 |
+
logging.StreamHandler()
|
26 |
+
])
|
27 |
+
logger = logging.getLogger(__name__)
|
28 |
+
|
29 |
+
# --- Helper function for updating the status ---
|
30 |
+
def update_status(script, status, file=None):
|
31 |
+
if file:
|
32 |
+
script.status = f"{status}: {file}"
|
33 |
+
print(f"{status}: {file}") # For console logging.
|
34 |
+
else:
|
35 |
+
script.status = status
|
36 |
+
print(status) # For console logging
|
37 |
+
|
38 |
+
# --- Git Related Functions ---
|
39 |
+
def clone_or_create_repo(repo_url, repo_path, script):
|
40 |
+
update_status(script, "Checking/Cloning Repo...")
|
41 |
+
if os.path.exists(repo_path) and os.path.isdir(repo_path):
|
42 |
+
logger.info(f"Repository already exists at {repo_path}, updating...")
|
43 |
+
repo = Repo(repo_path)
|
44 |
+
if repo.is_dirty():
|
45 |
+
logger.warning("Local repo has uncommitted changes. Commit those before running to make sure nothing breaks.")
|
46 |
+
update_status(script, "Local repo has uncommitted changes")
|
47 |
+
else:
|
48 |
+
logger.info(f"Cloning repository from {repo_url} to {repo_path}")
|
49 |
+
update_status(script, "Cloning repository")
|
50 |
+
try:
|
51 |
+
use_git_credential_store = shared.opts.data.get("git_credential_store", True)
|
52 |
+
if use_git_credential_store:
|
53 |
+
repo = Repo.clone_from(repo_url, repo_path)
|
54 |
+
else:
|
55 |
+
if "HF_TOKEN" not in os.environ:
|
56 |
+
update_status(script, "HF_TOKEN environment variable not found")
|
57 |
+
raise Exception("HF_TOKEN environment variable not found")
|
58 |
+
env_token = os.environ["HF_TOKEN"]
|
59 |
+
repo = Repo.clone_from(repo_url.replace("https://", f"https://{script.hf_user}:{env_token}@"), repo_path)
|
60 |
+
|
61 |
+
except Exception as e:
|
62 |
+
logger.error(f"Error creating or cloning repo: {e}")
|
63 |
+
update_status(script, f"Error creating or cloning repo: {e}")
|
64 |
+
raise
|
65 |
+
update_status(script, "Repo ready")
|
66 |
+
return repo
|
67 |
+
|
68 |
+
def git_push_files(repo_path, commit_message, script):
|
69 |
+
update_status(script, "Pushing changes...")
|
70 |
+
try:
|
71 |
+
repo = Repo(repo_path)
|
72 |
+
repo.git.add(all=True)
|
73 |
+
repo.index.commit(commit_message)
|
74 |
+
origin = repo.remote(name='origin')
|
75 |
+
use_git_credential_store = shared.opts.data.get("git_credential_store", True)
|
76 |
+
if use_git_credential_store:
|
77 |
+
origin.push()
|
78 |
+
else:
|
79 |
+
if "HF_TOKEN" not in os.environ:
|
80 |
+
update_status(script, "HF_TOKEN environment variable not found")
|
81 |
+
raise Exception("HF_TOKEN environment variable not found")
|
82 |
+
env_token = os.environ["HF_TOKEN"]
|
83 |
+
origin.push(f"https://{script.hf_user}:{env_token}@huggingface.co/{script.hf_user}/{REPO_NAME}")
|
84 |
+
|
85 |
+
logger.info(f"Changes pushed successfully to remote repository.")
|
86 |
+
update_status(script, "Pushing Complete")
|
87 |
+
except Exception as e:
|
88 |
+
logger.error(f"Git push failed: {e}")
|
89 |
+
update_status(script, f"Git push failed: {e}")
|
90 |
+
raise
|
91 |
+
|
92 |
+
# --- Backup Logic ---
|
93 |
+
def backup_files(paths, hf_client, script):
|
94 |
+
logger.info("Starting backup...")
|
95 |
+
update_status(script, "Starting Backup...")
|
96 |
+
repo_id = script.hf_user + "/" + REPO_NAME
|
97 |
+
repo_path = os.path.join(script.basedir, 'backup')
|
98 |
+
sd_path = script.sd_path
|
99 |
+
|
100 |
+
try:
|
101 |
+
repo = clone_or_create_repo(f"https://huggingface.co/{repo_id}", repo_path, script)
|
102 |
+
except Exception as e:
|
103 |
+
logger.error("Error starting the backup, please see the traceback.")
|
104 |
+
return
|
105 |
+
|
106 |
+
for base_path in paths:
|
107 |
+
logger.info(f"Backing up: {base_path}")
|
108 |
+
for root, _, files in os.walk(os.path.join(sd_path, base_path)):
|
109 |
+
for file in files:
|
110 |
+
local_file_path = os.path.join(root, file)
|
111 |
+
repo_file_path = os.path.relpath(local_file_path, start=sd_path)
|
112 |
+
try:
|
113 |
+
os.makedirs(os.path.dirname(os.path.join(repo_path, repo_file_path)), exist_ok=True)
|
114 |
+
shutil.copy2(local_file_path, os.path.join(repo_path, repo_file_path))
|
115 |
+
logger.info(f"Copied: {repo_file_path}")
|
116 |
+
update_status(script, "Copied", repo_file_path)
|
117 |
+
except Exception as e:
|
118 |
+
logger.error(f"Error copying {repo_file_path}: {e}")
|
119 |
+
update_status(script, f"Error copying: {repo_file_path}: {e}")
|
120 |
+
return
|
121 |
+
|
122 |
+
try:
|
123 |
+
git_push_files(repo_path, f"Backup at {datetime.datetime.now()}", script)
|
124 |
+
logger.info("Backup complete")
|
125 |
+
update_status(script, "Backup Complete")
|
126 |
+
except Exception as e:
|
127 |
+
logger.error("Error pushing to the repo: ", e)
|
128 |
+
return
|
129 |
+
|
130 |
+
def start_backup_thread(script):
|
131 |
+
threading.Thread(target=backup_files, args=(script.backup_paths, None, script), daemon=True).start()
|
132 |
+
|
133 |
+
# Gradio UI Setup
|
134 |
+
def on_ui(script):
|
135 |
+
with gr.Column():
|
136 |
+
with gr.Row():
|
137 |
+
with gr.Column(scale=3):
|
138 |
+
hf_token_box = gr.Textbox(label="Huggingface Token", type='password', value=script.hf_token)
|
139 |
+
def on_token_change(token):
|
140 |
+
script.hf_token = token
|
141 |
+
script.save()
|
142 |
+
hf_token_box.change(on_token_change, inputs=[hf_token_box], outputs=None)
|
143 |
+
with gr.Column(scale=1):
|
144 |
+
status_box = gr.Textbox(label="Status", value=script.status)
|
145 |
+
|
146 |
+
def on_start_button():
|
147 |
+
start_backup_thread(script)
|
148 |
+
return "Starting Backup"
|
149 |
+
|
150 |
+
start_button = gr.Button(value="Start Backup")
|
151 |
+
start_button.click(on_start_button, inputs=None, outputs=[status_box])
|
152 |
+
|
153 |
+
with gr.Row():
|
154 |
+
with gr.Column():
|
155 |
+
sd_path_box = gr.Textbox(label="SD Webui Path", value=script.sd_path)
|
156 |
+
def on_sd_path_change(path):
|
157 |
+
script.sd_path = path
|
158 |
+
script.save()
|
159 |
+
sd_path_box.change(on_sd_path_change, inputs=[sd_path_box], outputs=None)
|
160 |
+
with gr.Column():
|
161 |
+
hf_user_box = gr.Textbox(label="Huggingface Username", value=script.hf_user)
|
162 |
+
def on_hf_user_change(user):
|
163 |
+
script.hf_user = user
|
164 |
+
script.save()
|
165 |
+
hf_user_box.change(on_hf_user_change, inputs=[hf_user_box], outputs=None)
|
166 |
+
with gr.Row():
|
167 |
+
backup_paths_box = gr.Textbox(label="Backup Paths (one path per line)", lines=4, value='\n'.join(script.backup_paths))
|
168 |
+
def on_backup_paths_change(paths):
|
169 |
+
paths_list = paths.split('\n')
|
170 |
+
paths_list = [p.strip() for p in paths_list if p.strip()]
|
171 |
+
script.backup_paths = paths_list
|
172 |
+
script.save()
|
173 |
+
backup_paths_box.change(on_backup_paths_change, inputs=[backup_paths_box], outputs=None)
|
174 |
+
|
175 |
+
def on_run(script, p, *args):
|
176 |
+
pass
|
177 |
+
|
178 |
+
def on_script_load(script):
|
179 |
+
script.hf_token = script.load().get(HF_TOKEN_KEY, '')
|
180 |
+
script.backup_paths = script.load().get(BACKUP_PATHS_KEY, DEFAULT_BACKUP_PATHS)
|
181 |
+
script.sd_path = script.load().get(SD_PATH_KEY, '')
|
182 |
+
script.hf_user = script.load().get(HF_USER_KEY, '')
|
183 |
+
script.status = "Not running"
|
184 |
+
|
185 |
+
|
186 |
+
script_callbacks.on_ui_tabs(on_ui)
|
187 |
+
script_callbacks.on_script_load(on_script_load)
|
scripts/ui-settings.py
ADDED
@@ -0,0 +1,37 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import gradio as gr
|
2 |
+
from modules import shared, script_callbacks
|
3 |
+
|
4 |
+
def on_ui_settings():
|
5 |
+
section = ('huggingface', "Hugging Face")
|
6 |
+
shared.opts.add_option(
|
7 |
+
"hf_write_key",
|
8 |
+
shared.OptionInfo(
|
9 |
+
"",
|
10 |
+
"Hugging Face Write API Key",
|
11 |
+
gr.Password,
|
12 |
+
{"interactive": True},
|
13 |
+
section=section
|
14 |
+
)
|
15 |
+
)
|
16 |
+
shared.opts.add_option(
|
17 |
+
"hf_read_key",
|
18 |
+
shared.OptionInfo(
|
19 |
+
"",
|
20 |
+
"Hugging Face Read API Key",
|
21 |
+
gr.Password,
|
22 |
+
{"interactive": True},
|
23 |
+
section=section
|
24 |
+
)
|
25 |
+
)
|
26 |
+
shared.opts.add_option(
|
27 |
+
"git_credential_store",
|
28 |
+
shared.OptionInfo(
|
29 |
+
True,
|
30 |
+
"Use Git Credential Store",
|
31 |
+
gr.Checkbox,
|
32 |
+
{"interactive": True},
|
33 |
+
section=section
|
34 |
+
)
|
35 |
+
)
|
36 |
+
|
37 |
+
script_callbacks.on_ui_settings(on_ui_settings)
|