18 Commits
Author SHA1 Message Date
Demis Bellot cb7151a82f don't print downloading if file exists 2025-05-08 14:06:54 +08:00
Demis Bellot 40067d598f Add Wan2.1 workflow 2025-05-08 10:53:24 +08:00
Demis Bellot e9232aaa09 bump to v0.2 2025-05-08 10:47:09 +08:00
Demis Bellot 869f5f96c3 Update README.md 2025-05-08 10:45:02 +08:00
Demis Bellot 97b2512a92 Update README.md 2025-05-08 10:44:27 +08:00
Demis Bellot 4097dc0095 Change display name 2025-05-08 10:43:40 +08:00
Demis Bellot 3967237d8b Merge branch 'main' of https://github.com/ServiceStack/comfy-asset-downloader 2025-05-08 01:31:51 +08:00
Demis Bellot 127fdcbbe1 Update pyproject.toml 2025-05-08 01:31:49 +08:00
Demis Bellot 94068ed488 Merge pull request #1 from ComfyNodePRs/publish
Add Github Action for Publishing to Comfy Registry
2025-05-08 01:31:19 +08:00
Demis Bellot 5a989152e9 Merge pull request #2 from ComfyNodePRs/pyproject
Add pyproject.toml for Custom Node Registry
2025-05-08 01:22:49 +08:00
async () = process.env.GIT_USERNAME || (await ghUser()).email && (await ghUser()).name || DIE("Missing env.GIT_USERNAME") 0d5befb024 chore(pyproject): Add pyproject.toml for Custom Node Registry 2025-05-07 16:59:42 +00:00
async () = process.env.GIT_USERNAME || (await ghUser()).email && (await ghUser()).name || DIE("Missing env.GIT_USERNAME") b8c69c320c chore(publish): Add Github Action for Publishing to Comfy Registry 2025-05-07 16:59:41 +00:00
Demis Bellot 81ff3d8a6b Resolve models_dir from folder_paths and sanitize save_to to ensure it's within models_dir 2025-05-08 00:12:45 +08:00
Demis Bellot 78bee0e18d Update README.md 2025-05-07 16:28:52 +08:00
Demis Bellot 7ea4d3f557 Update README.md 2025-05-07 16:27:43 +08:00
Demis Bellot 1e7baafb50 Update asset_downloader.py 2025-05-07 16:22:00 +08:00
Demis Bellot 8591a0b29b Update README.md 2025-05-07 16:13:51 +08:00
Demis Bellot c8cdc48f6f Update README.md 2025-05-07 15:50:35 +08:00
5 changed files with 966 additions and 19 deletions
+28
View File
@@ -0,0 +1,28 @@
name: Publish to Comfy registry
on:
workflow_dispatch:
push:
branches:
- main
- master
paths:
- "pyproject.toml"
permissions:
issues: write
jobs:
publish-node:
name: Publish Custom Node to registry
runs-on: ubuntu-latest
if: ${{ github.repository_owner == 'ServiceStack' }}
steps:
- name: Check out code
uses: actions/checkout@v4
with:
submodules: true
- name: Publish Custom Node
uses: Comfy-Org/publish-node-action@v1
with:
## Add your own personal access token to your Github Repository secrets and reference it here.
personal_access_token: ${{ secrets.REGISTRY_ACCESS_TOKEN }}
+16 -5
View File
@@ -1,4 +1,4 @@
# ComfyUI Asset Downloader
# Comfy Asset Downloader
A ComfyUI custom node that allows you to download models and other assets used in ComfyUI workflows from external sources like Hugging Face and CivitAI directly within your workflows to make it easier to share workflows and save time finding and downloading all the assets needed.
@@ -10,12 +10,23 @@ A ComfyUI custom node that allows you to download models and other assets used i
- Visual progress indicator during downloads
- Embed download nodes directly in your workflows to ensure dependencies are available
[![](./examples/workflow.webp)](https://raw.githubusercontent.com/ServiceStack/comfy-asset-downloader/refs/heads/main/examples/workflow.webp)
## Installation
### Using ComfyUI Manager
Search and install **Comfy Asset Downloader** from [ComfyUI's Manager](https://github.com/Comfy-Org/ComfyUI-Manager) **Custom Nodes Manager**.
### Using Git
```bash
git clone https://github.com/ServiceStack/comfy-asset-downloader ComfyUI/custom_nodes/comfy-asset-downloader
git clone https://github.com/ServiceStack/comfy-asset-downloader \
ComfyUI/custom_nodes/comfy-asset-downloader
```
### Configure Environment Variables (Optional)
To download models and resources from CivitAI or protected Hugging Face repositories we recommend your ComfyUI
instance has these environment variables (e.g. by adding to `~/.profile`):
@@ -39,8 +50,6 @@ For ease of use and security the **token** can be prefixed with `$` to use an en
This will use the value of the `HF_TOKEN` environment variable if it exists.
[![](/examples/workflow.webp)](./examples/hidream-dev-face.png)
### Usage
1. Add an `AssetDownloader` node to your workflow
@@ -62,9 +71,11 @@ __(Drag image to ComfyUI)__
- [flux1-schnell-cyborg.json](./examples/flux1-schnell-cyborg.json): A workflow using the **FLUX.1-schnell** model from Hugging Face (requires HF_TOKEN)
- [epiCRealismXL-sparrow.json](./examples/epiCRealismXL-sparrow.json): A workflow that downloads the [epiCRealism XL](https://civitai.com/models/277058?modelVersionId=1522905) model from CivitAI (requires CivitAI token) for realistic image generation.
- [dreamshaperXL-forest-woman.json](./examples/dreamshaperXL-forest-woman.json): A workflow using [DreamShaper XL](https://civitai.com/models/112902/dreamshaper-xl) from CivitAI
- [jibMixRealisticXL-hamster.json](jibMixRealisticXL-hamster.json): A workflow that uses [Jib Mix Realistic XL](https://civitai.com/models/194768/jib-mix-realistic-xl) from CivitAI
- [jibMixRealisticXL-hamster.json](./examples/jibMixRealisticXL-hamster.json): A workflow that uses [Jib Mix Realistic XL](https://civitai.com/models/194768/jib-mix-realistic-xl) from CivitAI
- [sdxl_lightning-enchantress.json](./examples/sdxl_lightning-enchantress.json): A workflow that downloads **SDXL-Lightning** from Hugging Face for fast image generation using SDXL.
- [sd3.5_large_fp8-spider.json](./examples/sd3.5_large_fp8-spider.json): A workflow that downloads **Stable Diffusion 3.5 Large FP8** model from Hugging Face for a more resource efficient high quality SD 3.5 Large image generation
- [wan-image-to-video.json](./examples/wan-image-to-video.json.json): A workflow that downloads **Wan2.1** text-to-video model from Hugging Face for generative videos from an initial image and text prompt.
## Credits
+905
View File
@@ -0,0 +1,905 @@
{
"id": "5408edec-ec0f-4cc6-904f-2dd4ae4034d2",
"revision": 0,
"last_node_id": 58,
"last_link_id": 111,
"nodes": [
{
"id": 8,
"type": "VAEDecode",
"pos": [
1210,
190
],
"size": [
210,
46
],
"flags": {},
"order": 15,
"mode": 0,
"inputs": [
{
"name": "samples",
"type": "LATENT",
"link": 35
},
{
"name": "vae",
"type": "VAE",
"link": 76
}
],
"outputs": [
{
"name": "IMAGE",
"type": "IMAGE",
"slot_index": 0,
"links": [
56,
93
]
}
],
"properties": {
"cnr_id": "comfy-core",
"ver": "0.3.30",
"Node name for S&R": "VAEDecode"
},
"widgets_values": []
},
{
"id": 39,
"type": "VAELoader",
"pos": [
866.3932495117188,
499.18597412109375
],
"size": [
306.36004638671875,
58
],
"flags": {},
"order": 0,
"mode": 0,
"inputs": [],
"outputs": [
{
"name": "VAE",
"type": "VAE",
"slot_index": 0,
"links": [
76,
99
]
}
],
"properties": {
"cnr_id": "comfy-core",
"ver": "0.3.30",
"Node name for S&R": "VAELoader"
},
"widgets_values": [
"wan_2.1_vae.safetensors"
]
},
{
"id": 28,
"type": "SaveAnimatedWEBP",
"pos": [
1460,
190
],
"size": [
870.8511352539062,
643.7430419921875
],
"flags": {},
"order": 16,
"mode": 0,
"inputs": [
{
"name": "images",
"type": "IMAGE",
"link": 56
}
],
"outputs": [],
"properties": {
"cnr_id": "comfy-core",
"ver": "0.3.30"
},
"widgets_values": [
"ComfyUI",
16,
false,
90,
"default"
]
},
{
"id": 47,
"type": "SaveWEBM",
"pos": [
2367.213134765625,
193.6114959716797
],
"size": [
315,
130
],
"flags": {},
"order": 17,
"mode": 4,
"inputs": [
{
"name": "images",
"type": "IMAGE",
"link": 93
}
],
"outputs": [],
"properties": {
"cnr_id": "comfy-core",
"ver": "0.3.30",
"Node name for S&R": "SaveWEBM"
},
"widgets_values": [
"ComfyUI",
"vp9",
24,
32
]
},
{
"id": 7,
"type": "CLIPTextEncode",
"pos": [
413,
389
],
"size": [
425.27801513671875,
180.6060791015625
],
"flags": {},
"order": 12,
"mode": 0,
"inputs": [
{
"name": "clip",
"type": "CLIP",
"link": 75
}
],
"outputs": [
{
"name": "CONDITIONING",
"type": "CONDITIONING",
"slot_index": 0,
"links": [
98
]
}
],
"title": "CLIP Text Encode (Negative Prompt)",
"properties": {
"cnr_id": "comfy-core",
"ver": "0.3.30",
"Node name for S&R": "CLIPTextEncode"
},
"widgets_values": [
"色调艳丽,过曝,静态,细节模糊不清,字幕,风格,作品,画作,画面,静止,整体发灰,最差质量,低质量,JPEG压缩残留,丑陋的,残缺的,多余的手指,画得不好的手部,画得不好的脸部,畸形的,毁容的,形态畸形的肢体,手指融合,静止不动的画面,杂乱的背景,三条腿,背景人很多,倒着走"
],
"color": "#322",
"bgcolor": "#533"
},
{
"id": 50,
"type": "WanImageToVideo",
"pos": [
673.0507202148438,
627.272705078125
],
"size": [
342.5999755859375,
210
],
"flags": {},
"order": 13,
"mode": 0,
"inputs": [
{
"name": "positive",
"type": "CONDITIONING",
"link": 97
},
{
"name": "negative",
"type": "CONDITIONING",
"link": 98
},
{
"name": "vae",
"type": "VAE",
"link": 99
},
{
"name": "clip_vision_output",
"shape": 7,
"type": "CLIP_VISION_OUTPUT",
"link": 107
},
{
"name": "start_image",
"shape": 7,
"type": "IMAGE",
"link": 106
}
],
"outputs": [
{
"name": "positive",
"type": "CONDITIONING",
"slot_index": 0,
"links": [
101
]
},
{
"name": "negative",
"type": "CONDITIONING",
"slot_index": 1,
"links": [
102
]
},
{
"name": "latent",
"type": "LATENT",
"slot_index": 2,
"links": [
103
]
}
],
"properties": {
"cnr_id": "comfy-core",
"ver": "0.3.30",
"Node name for S&R": "WanImageToVideo"
},
"widgets_values": [
512,
512,
33,
1
]
},
{
"id": 3,
"type": "KSampler",
"pos": [
863,
187
],
"size": [
315,
262
],
"flags": {},
"order": 14,
"mode": 0,
"inputs": [
{
"name": "model",
"type": "MODEL",
"link": 111
},
{
"name": "positive",
"type": "CONDITIONING",
"link": 101
},
{
"name": "negative",
"type": "CONDITIONING",
"link": 102
},
{
"name": "latent_image",
"type": "LATENT",
"link": 103
}
],
"outputs": [
{
"name": "LATENT",
"type": "LATENT",
"slot_index": 0,
"links": [
35
]
}
],
"properties": {
"cnr_id": "comfy-core",
"ver": "0.3.30",
"Node name for S&R": "KSampler"
},
"widgets_values": [
117704270511261,
"randomize",
20,
6,
"uni_pc",
"simple",
1
]
},
{
"id": 49,
"type": "CLIPVisionLoader",
"pos": [
40.18779754638672,
812.57470703125
],
"size": [
315,
58
],
"flags": {},
"order": 1,
"mode": 0,
"inputs": [],
"outputs": [
{
"name": "CLIP_VISION",
"type": "CLIP_VISION",
"slot_index": 0,
"links": [
94
]
}
],
"properties": {
"cnr_id": "comfy-core",
"ver": "0.3.30",
"Node name for S&R": "CLIPVisionLoader"
},
"widgets_values": [
"clip_vision_h.safetensors"
]
},
{
"id": 52,
"type": "LoadImage",
"pos": [
40.18779754638672,
932.5743408203125
],
"size": [
315,
314
],
"flags": {},
"order": 2,
"mode": 0,
"inputs": [],
"outputs": [
{
"name": "IMAGE",
"type": "IMAGE",
"slot_index": 0,
"links": [
106,
109
]
},
{
"name": "MASK",
"type": "MASK",
"slot_index": 1,
"links": null
}
],
"properties": {
"cnr_id": "comfy-core",
"ver": "0.3.30",
"Node name for S&R": "LoadImage"
},
"widgets_values": [
"flux_dev_example.png",
"image"
]
},
{
"id": 54,
"type": "ModelSamplingSD3",
"pos": [
510,
70
],
"size": [
315,
58
],
"flags": {},
"order": 10,
"mode": 0,
"inputs": [
{
"name": "model",
"type": "MODEL",
"link": 110
}
],
"outputs": [
{
"name": "MODEL",
"type": "MODEL",
"slot_index": 0,
"links": [
111
]
}
],
"properties": {
"cnr_id": "comfy-core",
"ver": "0.3.30",
"Node name for S&R": "ModelSamplingSD3"
},
"widgets_values": [
8
]
},
{
"id": 51,
"type": "CLIPVisionEncode",
"pos": [
380.18817138671875,
812.57470703125
],
"size": [
253.60000610351562,
78
],
"flags": {},
"order": 9,
"mode": 0,
"inputs": [
{
"name": "clip_vision",
"type": "CLIP_VISION",
"link": 94
},
{
"name": "image",
"type": "IMAGE",
"link": 109
}
],
"outputs": [
{
"name": "CLIP_VISION_OUTPUT",
"type": "CLIP_VISION_OUTPUT",
"slot_index": 0,
"links": [
107
]
}
],
"properties": {
"cnr_id": "comfy-core",
"ver": "0.3.30",
"Node name for S&R": "CLIPVisionEncode"
},
"widgets_values": [
"none"
]
},
{
"id": 57,
"type": "AssetDownloader",
"pos": [
-243.1000213623047,
604.3241577148438
],
"size": [
289.6023254394531,
130
],
"flags": {},
"order": 3,
"mode": 0,
"inputs": [],
"outputs": [],
"properties": {
"cnr_id": "comfy-asset-downloader",
"ver": "78bee0e18db8917c5ea0ebddb592e13d3caa5472",
"Node name for S&R": "AssetDownloader",
"aux_id": "ServiceStack/comfy-asset-downloader"
},
"widgets_values": [
"https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged/resolve/main/split_files/clip_vision/clip_vision_h.safetensors",
"clip_vision",
"clip_vision_h.safetensors",
""
]
},
{
"id": 37,
"type": "UNETLoader",
"pos": [
29.76833152770996,
40.69501495361328
],
"size": [
346.7470703125,
82
],
"flags": {},
"order": 4,
"mode": 0,
"inputs": [],
"outputs": [
{
"name": "MODEL",
"type": "MODEL",
"slot_index": 0,
"links": [
110
]
}
],
"properties": {
"cnr_id": "comfy-core",
"ver": "0.3.30",
"Node name for S&R": "UNETLoader"
},
"widgets_values": [
"wan2.1_i2v_480p_14B_fp16.safetensors",
"default"
]
},
{
"id": 55,
"type": "AssetDownloader",
"pos": [
64.27653503417969,
412.86444091796875
],
"size": [
294.1608581542969,
130
],
"flags": {},
"order": 5,
"mode": 0,
"inputs": [],
"outputs": [],
"properties": {
"cnr_id": "comfy-asset-downloader",
"ver": "78bee0e18db8917c5ea0ebddb592e13d3caa5472",
"Node name for S&R": "AssetDownloader",
"aux_id": "ServiceStack/comfy-asset-downloader"
},
"widgets_values": [
"https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged/resolve/main/split_files/text_encoders/umt5_xxl_fp8_e4m3fn_scaled.safetensors",
"clip",
"umt5_xxl_fp8_e4m3fn_scaled.safetensors",
""
]
},
{
"id": 38,
"type": "CLIPLoader",
"pos": [
-11.909887313842773,
191.3024444580078
],
"size": [
390,
106
],
"flags": {},
"order": 6,
"mode": 0,
"inputs": [],
"outputs": [
{
"name": "CLIP",
"type": "CLIP",
"slot_index": 0,
"links": [
74,
75
]
}
],
"properties": {
"cnr_id": "comfy-core",
"ver": "0.3.30",
"Node name for S&R": "CLIPLoader"
},
"widgets_values": [
"umt5_xxl_fp8_e4m3fn_scaled.safetensors",
"wan",
"default"
]
},
{
"id": 58,
"type": "AssetDownloader",
"pos": [
67.1648941040039,
603.0263671875
],
"size": [
289.6023254394531,
130
],
"flags": {},
"order": 7,
"mode": 0,
"inputs": [],
"outputs": [],
"properties": {
"cnr_id": "comfy-asset-downloader",
"ver": "78bee0e18db8917c5ea0ebddb592e13d3caa5472",
"Node name for S&R": "AssetDownloader",
"aux_id": "ServiceStack/comfy-asset-downloader"
},
"widgets_values": [
"https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged/resolve/main/split_files/vae/wan_2.1_vae.safetensors",
"vae",
"wan_2.1_vae.safetensors",
""
]
},
{
"id": 6,
"type": "CLIPTextEncode",
"pos": [
415,
186
],
"size": [
422.84503173828125,
164.31304931640625
],
"flags": {},
"order": 11,
"mode": 0,
"inputs": [
{
"name": "clip",
"type": "CLIP",
"link": 74
}
],
"outputs": [
{
"name": "CONDITIONING",
"type": "CONDITIONING",
"slot_index": 0,
"links": [
97
]
}
],
"title": "CLIP Text Encode (Positive Prompt)",
"properties": {
"cnr_id": "comfy-core",
"ver": "0.3.30",
"Node name for S&R": "CLIPTextEncode"
},
"widgets_values": [
"a cute anime girl with massive fennec ears and a big fluffy tail wearing a maid outfit turning around"
],
"color": "#232",
"bgcolor": "#353"
},
{
"id": 56,
"type": "AssetDownloader",
"pos": [
-243.1000213623047,
412.86444091796875
],
"size": [
289.60235595703125,
130
],
"flags": {},
"order": 8,
"mode": 0,
"inputs": [],
"outputs": [],
"properties": {
"cnr_id": "comfy-asset-downloader",
"ver": "78bee0e18db8917c5ea0ebddb592e13d3caa5472",
"Node name for S&R": "AssetDownloader",
"aux_id": "ServiceStack/comfy-asset-downloader"
},
"widgets_values": [
"https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged/resolve/main/split_files/diffusion_models/wan2.1_i2v_480p_14B_fp16.safetensors",
"diffusion_models",
"wan2.1_i2v_480p_14B_fp16.safetensors",
""
]
}
],
"links": [
[
35,
3,
0,
8,
0,
"LATENT"
],
[
56,
8,
0,
28,
0,
"IMAGE"
],
[
74,
38,
0,
6,
0,
"CLIP"
],
[
75,
38,
0,
7,
0,
"CLIP"
],
[
76,
39,
0,
8,
1,
"VAE"
],
[
93,
8,
0,
47,
0,
"IMAGE"
],
[
94,
49,
0,
51,
0,
"CLIP_VISION"
],
[
97,
6,
0,
50,
0,
"CONDITIONING"
],
[
98,
7,
0,
50,
1,
"CONDITIONING"
],
[
99,
39,
0,
50,
2,
"VAE"
],
[
101,
50,
0,
3,
1,
"CONDITIONING"
],
[
102,
50,
1,
3,
2,
"CONDITIONING"
],
[
103,
50,
2,
3,
3,
"LATENT"
],
[
106,
52,
0,
50,
4,
"IMAGE"
],
[
107,
51,
0,
50,
3,
"CLIP_VISION_OUTPUT"
],
[
109,
52,
0,
51,
1,
"IMAGE"
],
[
110,
37,
0,
54,
0,
"MODEL"
],
[
111,
54,
0,
3,
0,
"MODEL"
]
],
"groups": [
{
"id": 1,
"title": "Download Dependencies",
"bounding": [
-261.3348083496094,
325.6006774902344,
639.4381713867188,
420.1655578613281
],
"color": "#3f789e",
"font_size": 24,
"flags": {}
}
],
"config": {},
"extra": {
"ds": {
"scale": 1.2284597357367768,
"offset": [
299.14736712925094,
115.10858926092027
]
},
"frontendVersion": "1.17.11"
},
"version": 0.4
}
+13 -10
View File
@@ -5,14 +5,10 @@ import shutil
import hashlib
from tqdm import tqdm
from server import PromptServer
def models_dir():
current_dir = os.path.dirname(os.path.abspath(__file__))
base_dir = os.path.dirname(os.path.dirname(os.path.dirname(current_dir)))
return os.path.join(base_dir, 'models')
from folder_paths import models_dir
def model_folders():
return immediate_folders(models_dir())
return immediate_folders(models_dir)
def immediate_folders(directory_path):
"""
@@ -22,13 +18,13 @@ def immediate_folders(directory_path):
for d in os.listdir(directory_path):
if os.path.isdir(os.path.join(directory_path, d)):
name = os.path.basename(d)
if name.startswith('.') or len(name) > 30:
if name.startswith('.'):
continue
folders.append(d)
for sd in os.listdir(os.path.join(directory_path, d)):
if os.path.isdir(os.path.join(directory_path, d, sd)):
name = os.path.basename(sd)
if name.startswith('.') or len(name) > 30:
if name.startswith('.'):
continue
folders.append(os.path.join(d, sd))
return folders
@@ -64,9 +60,16 @@ class AssetDownloader:
if not url or not save_to or not filename:
print(f"AssetDownloader: Missing required values: url='{url}', save_to='{save_to}', filename='{filename}'")
return ()
# Sanitize save_to to ensure it's within models_dir
safe_save_to = os.path.normpath(os.path.join(models_dir, save_to))
if not safe_save_to.startswith(models_dir):
print(f"AssetDownloader: Invalid save_to path. Must be within {models_dir}")
return ()
save_to = os.path.relpath(safe_save_to, models_dir)
relative_path = os.path.join(save_to, filename)
save_path = os.path.join(models_dir(), relative_path)
save_path = os.path.join(models_dir, relative_path)
if os.path.exists(save_path):
print(f"AssetDownloader: File already exists: {relative_path}")
return ()
@@ -111,6 +114,7 @@ class AssetDownloader:
})
shutil.move(temp_path, save_path)
print(f"Complete! {filename} saved to {save_path}")
return save_path
except Exception as e:
@@ -119,4 +123,3 @@ class AssetDownloader:
raise e
return ()
+4 -4
View File
@@ -1,7 +1,7 @@
[project]
name = "comfy-asset-downloader"
description = ""
version = "0.1"
description = "This node allows downloading models and other resources used within a ComfyUI workflow making it easier to resolve referenced assets and share workflows"
version = "0.3"
license = {file = "LICENSE"}
[project.urls]
@@ -9,6 +9,6 @@ Repository = "https://github.com/ServiceStack/comfy-asset-downloader"
# Used by Comfy Registry https://comfyregistry.org
[tool.comfy]
PublisherId = "ServiceStack"
DisplayName = "comfy-asset-downloader"
PublisherId = "servicestack"
DisplayName = "Comfy Asset Downloader"
Icon = ""