|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
import os
|
|
|
import time
|
|
|
import zipfile
|
|
|
import tempfile
|
|
|
|
|
|
import folder_paths
|
|
|
|
|
|
try:
|
|
|
from huggingface_hub import HfApi
|
|
|
except ImportError:
|
|
|
HfApi = None
|
|
|
|
|
|
|
|
|
class ZipOutputToHuggingFace:
|
|
|
"""
|
|
|
Zip the ComfyUI output folder and upload the archive to Hugging Face.
|
|
|
"""
|
|
|
|
|
|
@classmethod
|
|
|
def INPUT_TYPES(cls):
|
|
|
return {
|
|
|
"required": {
|
|
|
|
|
|
"hf_repo_id": (
|
|
|
"STRING",
|
|
|
{"default": "saliacoel/MyCustomNodes"},
|
|
|
),
|
|
|
},
|
|
|
"optional": {
|
|
|
|
|
|
"hf_token": (
|
|
|
"STRING",
|
|
|
{
|
|
|
"default": "",
|
|
|
"multiline": False,
|
|
|
},
|
|
|
),
|
|
|
|
|
|
"zip_name_prefix": (
|
|
|
"STRING",
|
|
|
{
|
|
|
"default": "comfy_output",
|
|
|
"multiline": False,
|
|
|
},
|
|
|
),
|
|
|
|
|
|
"remote_dir": (
|
|
|
"STRING",
|
|
|
{
|
|
|
"default": "exports",
|
|
|
"multiline": False,
|
|
|
},
|
|
|
),
|
|
|
},
|
|
|
}
|
|
|
|
|
|
RETURN_TYPES = ("STRING",)
|
|
|
RETURN_NAMES = ("download_url",)
|
|
|
FUNCTION = "zip_and_upload"
|
|
|
CATEGORY = "utils/huggingface"
|
|
|
OUTPUT_NODE = True
|
|
|
|
|
|
def zip_and_upload(
|
|
|
self,
|
|
|
hf_repo_id: str,
|
|
|
hf_token: str = "",
|
|
|
zip_name_prefix: str = "comfy_output",
|
|
|
remote_dir: str = "exports",
|
|
|
):
|
|
|
|
|
|
if HfApi is None:
|
|
|
raise RuntimeError(
|
|
|
"huggingface_hub is not installed.\n"
|
|
|
"Install it in your ComfyUI environment, for example:\n"
|
|
|
" pip install huggingface_hub"
|
|
|
)
|
|
|
|
|
|
|
|
|
output_dir = folder_paths.get_output_directory()
|
|
|
if not os.path.isdir(output_dir):
|
|
|
raise RuntimeError(f"Output directory does not exist: {output_dir}")
|
|
|
|
|
|
|
|
|
has_files = False
|
|
|
for _, _, files in os.walk(output_dir):
|
|
|
if files:
|
|
|
has_files = True
|
|
|
break
|
|
|
if not has_files:
|
|
|
raise RuntimeError(
|
|
|
f"No files found in output directory: {output_dir}"
|
|
|
)
|
|
|
|
|
|
|
|
|
timestamp = time.strftime("%Y%m%d-%H%M%S")
|
|
|
safe_prefix = (zip_name_prefix or "").strip() or "comfy_output"
|
|
|
zip_filename = f"{safe_prefix}_{timestamp}.zip"
|
|
|
|
|
|
tmp_dir = tempfile.gettempdir()
|
|
|
zip_path = os.path.join(tmp_dir, zip_filename)
|
|
|
zip_abs = os.path.abspath(zip_path)
|
|
|
|
|
|
|
|
|
with zipfile.ZipFile(zip_path, "w", zipfile.ZIP_DEFLATED) as zipf:
|
|
|
for root, _, files in os.walk(output_dir):
|
|
|
for fname in files:
|
|
|
file_path = os.path.join(root, fname)
|
|
|
|
|
|
if os.path.abspath(file_path) == zip_abs:
|
|
|
continue
|
|
|
rel_path = os.path.relpath(file_path, output_dir)
|
|
|
zipf.write(file_path, arcname=rel_path)
|
|
|
|
|
|
|
|
|
repo_id = (hf_repo_id or "").strip()
|
|
|
if not repo_id:
|
|
|
|
|
|
try:
|
|
|
os.remove(zip_path)
|
|
|
except OSError:
|
|
|
pass
|
|
|
raise RuntimeError("hf_repo_id cannot be empty")
|
|
|
|
|
|
remote_dir = (remote_dir or "").strip().strip("/")
|
|
|
if remote_dir:
|
|
|
path_in_repo = f"{remote_dir}/{zip_filename}"
|
|
|
else:
|
|
|
path_in_repo = zip_filename
|
|
|
|
|
|
api = HfApi()
|
|
|
|
|
|
|
|
|
try:
|
|
|
api.upload_file(
|
|
|
path_or_fileobj=zip_path,
|
|
|
path_in_repo=path_in_repo,
|
|
|
repo_id=repo_id,
|
|
|
|
|
|
token=hf_token or None,
|
|
|
)
|
|
|
except Exception as e:
|
|
|
|
|
|
try:
|
|
|
os.remove(zip_path)
|
|
|
except OSError:
|
|
|
pass
|
|
|
raise RuntimeError(f"Failed to upload to Hugging Face: {e}")
|
|
|
|
|
|
|
|
|
|
|
|
download_url = f"https://huggingface.co/{repo_id}/resolve/main/{path_in_repo}"
|
|
|
|
|
|
|
|
|
try:
|
|
|
os.remove(zip_path)
|
|
|
except OSError:
|
|
|
pass
|
|
|
|
|
|
|
|
|
return (download_url,)
|
|
|
|
|
|
|
|
|
|
|
|
NODE_CLASS_MAPPINGS = {
|
|
|
"ZipOutputToHuggingFace": ZipOutputToHuggingFace,
|
|
|
}
|
|
|
|
|
|
NODE_DISPLAY_NAME_MAPPINGS = {
|
|
|
"ZipOutputToHuggingFace": "Zip Output → Hugging Face",
|
|
|
}
|
|
|
|