Compare commits

..
8 Commits
Author SHA1 Message Date
Frost Ming e5d047fcfe fix: don't start comfyui when unpacking
Signed-off-by: Frost Ming <me@frostming.com>
2025-05-16 16:35:50 +08:00
Frost Ming 197ab89986 fix: no venv
Signed-off-by: Frost Ming <me@frostming.com>
2025-05-16 15:52:50 +08:00
Frost Ming b95ea5f6f2 fix: no venv when unpacking bento
Signed-off-by: Frost Ming <me@frostming.com>
2025-05-16 15:42:04 +08:00
Frost Ming 8d0621cc76 fix: don't fetch sources when building bento
Signed-off-by: Frost Ming <me@frostming.com>
2025-05-16 15:33:04 +08:00
Frost Ming b5244d176e fix: manage node detection
Signed-off-by: Frost Ming <me@frostming.com>
2025-05-13 11:38:16 +08:00
Frost Ming c67202511d fix: remove last newline
Signed-off-by: Frost Ming <me@frostming.com>
2025-05-13 11:29:03 +08:00
Frost Ming ee9b7aa187 feat: pack and unpack bento
Signed-off-by: Frost Ming <me@frostming.com>
2025-05-13 10:40:59 +08:00
Frost Ming ab98880429 feat: new packing and unpacking methods
Signed-off-by: Frost Ming <me@frostming.com>
2025-05-12 14:29:19 +08:00
4 changed files with 19 additions and 68 deletions
-7
View File
@@ -73,13 +73,6 @@ To unpack:
comfy-pack unpack workflow.cpack.zip
```
Huggingface gated models can be accessed by setting your `HF_TOKEN` as an environment variable before unpacking:
```bash
export HF_TOKEN=hf_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXx
comfy-pack unpack workflow.cpack.zip
```
For example cpack files, check our [examples folder](examples/).
### Deploy a workflow as an API
+6 -38
View File
@@ -1,18 +1,17 @@
import hashlib
import json
import os
import shutil
import sys
import shutil
import zipfile
from io import BytesIO
import folder_paths
import node_helpers
import numpy as np
import torch
from comfy_extras.nodes_audio import SaveAudio
from PIL import Image, ImageOps, ImageSequence, PngImagePlugin
from PIL.PngImagePlugin import PngInfo
from io import BytesIO
from .monkeypatch import set_bentoml_output
@@ -195,18 +194,9 @@ class OutputImageWithStringTxt:
OUTPUT_NODE = True
CATEGORY = "ComfyPack/output"
DESCRIPTION = (
"Saves the input images (and optional text) to your ComfyUI output directory."
)
DESCRIPTION = "Saves the input images (and optional text) to your ComfyUI output directory."
def save_images(
self,
images,
filename_prefix="cpack_output_",
text="",
prompt=None,
extra_pnginfo=None,
):
def save_images(self, images, filename_prefix="cpack_output_", text="", prompt=None, extra_pnginfo=None):
filename_prefix += self.prefix_append
full_output_folder, filename, counter, subfolder, filename_prefix = (
get_save_image_path(
@@ -219,7 +209,7 @@ class OutputImageWithStringTxt:
zip_path = os.path.join(full_output_folder, zip_filename)
# create ZIP file
with zipfile.ZipFile(zip_path, "w", zipfile.ZIP_DEFLATED) as zipf:
with zipfile.ZipFile(zip_path, 'w', zipfile.ZIP_DEFLATED) as zipf:
for batch_number, image in enumerate(images):
# temp store img to RAM
i = 255.0 * image.cpu().numpy()
@@ -235,12 +225,7 @@ class OutputImageWithStringTxt:
# write img file to RAM buffer
img_buffer = BytesIO()
img.save(
img_buffer,
format="PNG",
pnginfo=metadata,
compress_level=self.compress_level,
)
img.save(img_buffer, format="PNG", pnginfo=metadata, compress_level=self.compress_level)
img_buffer.seek(0)
# write img into ZIP file
@@ -450,25 +435,9 @@ class AnyInput:
return True
class OutputAudio(SaveAudio):
CPACK_NODE = True
CATEGORY = "ComfyPack/output"
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"audio": ("AUDIO",),
"filename_prefix": ("STRING", {"default": "cpack_output_"}),
},
"hidden": {"prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO"},
}
NODE_CLASS_MAPPINGS = {
"CPackOutputFile": OutputFile,
"CPackOutputImage": OutputImage,
"CPackOutputAudio": OutputAudio,
"CPackOutputZip": OutputImageWithStringTxt,
"CPackInputImage": ImageInput,
"CPackInputString": StringInput,
@@ -484,7 +453,6 @@ NODE_DISPLAY_NAME_MAPPINGS = {
"CPackInputFile": "File Input",
"CPackInputAny": "Any Input",
"CPackOutputImage": "Image Output",
"CPackOutputAudio": "Audio Output",
"CPackOutputFile": "File Output",
"CPackOutputZip": "Zip Output(img + txt file)",
}
+8 -6
View File
@@ -1,9 +1,8 @@
from .const import MODEL_SOURCE_CACHE_FILE
import asyncio
import json
import re
from .const import MODEL_SOURCE_CACHE_FILE
# MODEL_NAME = r"[a-zA-Z0-9-._]+"
# COMMIT = r"[a-f0-9]+"
@@ -15,8 +14,9 @@ PATH_PATTERN = re.compile(
async def _lookup_huggingface_model(model_sha: str) -> dict:
import aiohttp
from duckduckgo_search import DDGS
import aiohttp
query = f"site:huggingface.co blob {model_sha}"
try:
@@ -25,7 +25,7 @@ async def _lookup_huggingface_model(model_sha: str) -> dict:
async with aiohttp.ClientSession(trust_env=True) as session:
for result in search_results:
url = result['href']
url = result['link']
if "blob" not in url:
continue
@@ -38,9 +38,11 @@ async def _lookup_huggingface_model(model_sha: str) -> dict:
repo, commit = commit_match.groups()
if path_match := PATH_PATTERN.search(text):
path = path_match.group(1)
download_url = f"https://huggingface.co/{repo}/resolve/{commit}/{path}?download=true"
url = f"https://huggingface.co/{repo}/blob/{commit}/{path}"
info = {
"download_url": path,
"url": path,
"download_url": download_url,
"url": url,
"repo": repo,
"commit": commit,
"path": path,
+5 -17
View File
@@ -218,25 +218,13 @@ def get_search_url(sha: str) -> str:
def download_file(url: str, dest_path: Path, progress_callback=None):
"""Download file with progress tracking"""
# prepare auth token from huggingface if possible
if (token := os.getenv("HF_TOKEN")) and ("huggingface" in url):
bearer = f"Bearer {token}"
urllib_request = urllib.request.Request(
url, headers={"Authorization": bearer}
if subprocess.call(["curl", "--version"], stdout=subprocess.DEVNULL) == 0:
subprocess.check_call(
["curl", "-L", url, "-o", str(dest_path)],
)
curl_auth = ["-H", f"Authorization: {bearer}"]
else:
curl_auth = []
urllib_request = url
return True
try:
if shutil.which("curl"):
subprocess.check_call(
["curl", "-L", url, *curl_auth, "--fail", "-o", str(dest_path)],
)
return True
with urllib.request.urlopen(urllib_request) as response:
with urllib.request.urlopen(url) as response:
total_size = int(response.headers.get("content-length", 0))
block_size = 8192
downloaded = 0