Files
2024-12-04 10:45:27 +00:00

199 lines
6.5 KiB
Python

import os
import comfy.sd
import comfy.utils
from huggingface_hub import hf_hub_download
import requests
import hashlib
def download_file(url, filename):
# make dirs recursively
os.makedirs(os.path.dirname(filename), exist_ok=True)
response = requests.get(url, allow_redirects=True)
if response.status_code == 200:
with open(filename, 'wb') as file:
file.write(response.content)
else:
raise Exception(f"Failed to download file. Status code: {response.status_code}")
def get_filename_from_url(url: str, extension: str):
# md5 hash the url to get a unique filename
filename = hashlib.md5(url.encode()).hexdigest()
return f"{filename}.{extension}"
def find_or_create_cache():
cwd = os.getcwd()
if os.path.exists(os.path.join(cwd, "ComfyUI")):
cwd = os.path.join(cwd, "ComfyUI")
if os.path.exists(os.path.join(cwd, "models")):
cwd = os.path.join(cwd, "models")
if not os.path.exists(os.path.join(cwd, "huggingface_cache")):
print("Creating huggingface_cache directory within comfy")
os.mkdir(os.path.join(cwd, "huggingface_cache"))
return str(os.path.join(cwd, "huggingface_cache"))
def get_lora_from_url(url: str):
# clean the url
url = url.strip()
# make sure it is a url
if url is None or url == "":
raise ValueError("URL is empty")
if not url.startswith("http"):
raise ValueError("Invalid URL")
if "huggingface.co" in url:
# use hf hub download because it is faster
# strip the download=true query parameter - HF
if url.endswith("?download=true"):
url = url[:-len("?download=true")]
# handle user copying the blob url - HF
if '/blob/' in url:
url = url.replace('/blob/', '/resolve/')
# https://huggingface.co/owner_name/repo_name/resolve/main/file_name.safetensors
if not url.lower().endswith("safetensors"):
raise ValueError("Only safetensors files are supported.")
# parse hf repo from url
parser_string = url.replace("http://", "").replace("https://", "")
parts = parser_string.split("/")
# ['huggingface.co', 'owner_name', 'repo_name', 'resolve', 'main', 'file_name.safetensors']
# remove chunks that are not needed
repo_id = parts[1] + "/" + parts[2]
filename = parts[-1]
# extract any depth of subfolders
# ['huggingface.co', 'owner_name', 'repo_name', 'resolve', 'main', 'subfolder', 'sub-subfolder', 'file_name.safetensors']
subfolder = None
subfolder_length = len(parts) - 6
if subfolder_length > 0:
subfolder = "/".join(parts[5:-1])
lora_path = hf_hub_download(
repo_id=repo_id.strip(),
subfolder=subfolder,
filename=filename.strip(),
cache_dir=find_or_create_cache(),
)
elif "civitai.com" in url:
# good https://civitai.com/api/download/models/0000000000?type=Model&format=SafeTensor
# bad https://civitai.com/models/111111111111?modelVersionId=0000000000
# fix bad url
if "modelVersionId" in url:
model_version_id = url.split("modelVersionId=")[1]
model_version_id = model_version_id.split("&")[0]
url = f"https://civitai.com/api/download/models/{model_version_id}?type=Model&format=SafeTensor"
if not "SafeTensor" in url:
raise ValueError("Only safetensors files are supported for security reasons.")
save_filename = get_filename_from_url(url, "safetensors")
# check if env var for CIVITAI_API_KEY is set. Add after hashing the url
if "CIVITAI_API_KEY" in os.environ:
api_key = os.environ["CIVITAI_API_KEY"]
if api_key.strip() != "":
# add the api key to the url
url += f"&token={api_key}"
# download the file
lora_path = os.path.join(find_or_create_cache(), 'civitai', save_filename)
# check if the file already exists
if not os.path.exists(lora_path):
# will fail for most if api key not set
download_file(url, lora_path)
else:
# could be stored somewhere else
if not url.lower().split("?")[0].endswith("safetensors"):
raise ValueError("Only safetensors files are supported for security reasons.")
# try to download the file
save_filename = get_filename_from_url(url, "safetensors")
lora_path = os.path.join(find_or_create_cache(), 'general', save_filename)
# check if the file already exists
if not os.path.exists(lora_path):
download_file(url, lora_path)
return lora_path
class LoadLoraModelOnlyWithUrl:
CATEGORY = "loaders"
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"url": ("STRING", {"default": ""}),
"model": ("MODEL",),
"strength_model": (
"FLOAT",
{"default": 1.0, "min": -20.0, "max": 20.0, "step": 0.01},
),
},
}
RETURN_TYPES = ("MODEL",)
FUNCTION = "load_lora_model_only_from_url"
def __init__(self):
self.loaded_lora = None
self.loaded_lora_path = None
def load_lora_model_only_from_url(
self,
model,
url: str,
strength_model: float,
):
if strength_model == 0:
return (model, )
lora_path = get_lora_from_url(
url,
)
lora = None
if self.loaded_lora is not None:
if self.loaded_lora_path == lora_path:
lora = self.loaded_lora
else:
temp = self.loaded_lora
self.loaded_lora = None
del temp
self.loaded_lora_path = None
if lora is None:
lora = comfy.utils.load_torch_file(
lora_path, safe_load=True)
self.loaded_lora = lora
self.loaded_lora_path = lora_path
model_lora, _ = comfy.sd.load_lora_for_models(
model,
None,
lora,
strength_model,
0,
)
return (model_lora, )
NODE_CLASS_MAPPINGS = {
"LoadLoraModelOnlyWithUrl": LoadLoraModelOnlyWithUrl,
}
NODE_DISPLAY_NAME_MAPPINGS = {
"LoadLoraModelOnlyWithUrl": "Load Lora Model Only from URL",
}