Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1a8e089c7d | ||
|
|
557799cdb1 | ||
|
|
d5f52131db | ||
|
|
755399a255 | ||
|
|
ee3717b4b3 |
@@ -0,0 +1,137 @@
|
||||
import folder_paths
|
||||
|
||||
|
||||
class AnyType(str):
|
||||
def __ne__(self, __value: object) -> bool:
|
||||
return False
|
||||
|
||||
|
||||
WILDCARD = AnyType("*")
|
||||
|
||||
|
||||
class ComfyUIDeployExternalFile:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {
|
||||
"required": {
|
||||
"input_id": (
|
||||
"STRING",
|
||||
{"multiline": False, "default": "input_file"},
|
||||
),
|
||||
},
|
||||
"optional": {
|
||||
"display_name": (
|
||||
"STRING",
|
||||
{"multiline": False, "default": ""},
|
||||
),
|
||||
"description": (
|
||||
"STRING",
|
||||
{"multiline": True, "default": ""},
|
||||
),
|
||||
"file_url": (
|
||||
"STRING",
|
||||
{"multiline": False, "default": ""},
|
||||
),
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = (WILDCARD,)
|
||||
RETURN_NAMES = ("path",)
|
||||
FUNCTION = "run"
|
||||
CATEGORY = "🔗ComfyDeploy"
|
||||
|
||||
def run(
|
||||
self,
|
||||
input_id,
|
||||
display_name=None,
|
||||
description=None,
|
||||
file_url=None,
|
||||
):
|
||||
import requests
|
||||
import os
|
||||
import uuid
|
||||
from urllib.parse import urlparse
|
||||
|
||||
if file_url:
|
||||
if file_url.startswith("http"):
|
||||
# Use cache directory for saving files
|
||||
cache_dir = folder_paths.get_temp_directory()
|
||||
if not os.path.exists(cache_dir):
|
||||
os.makedirs(cache_dir)
|
||||
|
||||
# Always generate random filename to avoid conflicts
|
||||
parsed_url = urlparse(file_url)
|
||||
original_filename = os.path.basename(parsed_url.path)
|
||||
|
||||
# Extract file extension from original filename if available
|
||||
file_extension = ""
|
||||
if original_filename and "." in original_filename:
|
||||
file_extension = os.path.splitext(original_filename)[1]
|
||||
else:
|
||||
# Try to determine extension from content-type if no extension found
|
||||
file_extension = ".bin"
|
||||
|
||||
# Generate random filename with preserved extension
|
||||
filename = str(uuid.uuid4()) + file_extension
|
||||
|
||||
destination_path = os.path.join(cache_dir, filename)
|
||||
print(f"Cache directory: {cache_dir}")
|
||||
print(f"Destination path: {destination_path}")
|
||||
print(
|
||||
"Downloading external file - "
|
||||
+ file_url
|
||||
+ " to "
|
||||
+ destination_path
|
||||
)
|
||||
|
||||
headers = {"User-Agent": "Mozilla/5.0"}
|
||||
|
||||
try:
|
||||
response = requests.get(
|
||||
file_url,
|
||||
headers=headers,
|
||||
allow_redirects=True,
|
||||
timeout=30, # Add timeout to prevent hanging
|
||||
)
|
||||
response.raise_for_status()
|
||||
|
||||
with open(destination_path, "wb") as out_file:
|
||||
out_file.write(response.content)
|
||||
print(f"External file downloaded: {file_url} to {destination_path}")
|
||||
return (destination_path,)
|
||||
|
||||
except requests.exceptions.HTTPError as e:
|
||||
error_msg = f"HTTP Error {e.response.status_code}: {e.response.reason} for URL: {file_url}"
|
||||
print(f"⚠️ Download failed - {error_msg}")
|
||||
if e.response.status_code == 404:
|
||||
print(
|
||||
"💡 This URL might have expired or the file may have been deleted"
|
||||
)
|
||||
# Return empty string instead of crashing
|
||||
return ("",)
|
||||
|
||||
except requests.exceptions.RequestException as e:
|
||||
error_msg = (
|
||||
f"Network error downloading file from {file_url}: {str(e)}"
|
||||
)
|
||||
print(f"⚠️ Download failed - {error_msg}")
|
||||
return ("",)
|
||||
|
||||
except Exception as e:
|
||||
error_msg = (
|
||||
f"Unexpected error downloading file from {file_url}: {str(e)}"
|
||||
)
|
||||
print(f"⚠️ Download failed - {error_msg}")
|
||||
return ("",)
|
||||
else:
|
||||
print(f"External file loading: {file_url}")
|
||||
return (file_url,)
|
||||
else:
|
||||
print(f"No file URL provided")
|
||||
return ("",)
|
||||
|
||||
|
||||
NODE_CLASS_MAPPINGS = {"ComfyUIDeployExternalFile": ComfyUIDeployExternalFile}
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"ComfyUIDeployExternalFile": "External File (ComfyUI Deploy)"
|
||||
}
|
||||
@@ -0,0 +1,168 @@
|
||||
import folder_paths
|
||||
import os
|
||||
import shutil
|
||||
import uuid
|
||||
|
||||
|
||||
class AnyType(str):
|
||||
def __ne__(self, __value: object) -> bool:
|
||||
return False
|
||||
|
||||
|
||||
WILDCARD = AnyType("*")
|
||||
|
||||
|
||||
class ComfyDeployOutputFile:
|
||||
def __init__(self):
|
||||
self.output_dir = folder_paths.get_output_directory()
|
||||
self.type = "output"
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {
|
||||
"required": {
|
||||
"file_path": (
|
||||
"STRING",
|
||||
{
|
||||
"forceInput": True,
|
||||
"tooltip": "Path to the file to output and upload.",
|
||||
},
|
||||
),
|
||||
},
|
||||
"optional": {
|
||||
"output_id": (
|
||||
"STRING",
|
||||
{"multiline": False, "default": "output_file"},
|
||||
),
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ()
|
||||
FUNCTION = "run"
|
||||
OUTPUT_NODE = True
|
||||
CATEGORY = "🔗ComfyDeploy"
|
||||
DESCRIPTION = "Outputs any file by path for upload to ComfyDeploy."
|
||||
|
||||
def run(self, file_path, output_id="output_file"):
|
||||
if not file_path or not os.path.exists(file_path):
|
||||
print(f"⚠️ File not found: {file_path}")
|
||||
return {"ui": {"files": []}}
|
||||
|
||||
# Security checks - ensure file is within safe ComfyUI paths
|
||||
try:
|
||||
# Get absolute paths for comparison
|
||||
file_abs_path = os.path.abspath(file_path)
|
||||
base_path = folder_paths.base_path
|
||||
temp_dir = folder_paths.get_temp_directory()
|
||||
|
||||
# Check if file is within ComfyUI base path or temp directory
|
||||
if not (
|
||||
file_abs_path.startswith(os.path.abspath(base_path))
|
||||
or file_abs_path.startswith(os.path.abspath(temp_dir))
|
||||
):
|
||||
print(f"⚠️ Security: File outside allowed ComfyUI paths: {file_path}")
|
||||
return {"ui": {"files": []}}
|
||||
|
||||
# Check for path traversal attempts (but allow absolute paths within ComfyUI)
|
||||
if ".." in file_path:
|
||||
print(f"⚠️ Security: Path traversal attempt detected: {file_path}")
|
||||
return {"ui": {"files": []}}
|
||||
|
||||
except Exception as e:
|
||||
print(f"⚠️ Security check failed: {str(e)}")
|
||||
return {"ui": {"files": []}}
|
||||
|
||||
# Get the original filename and extension
|
||||
original_filename = os.path.basename(file_path)
|
||||
file_extension = os.path.splitext(original_filename)[1]
|
||||
|
||||
# Additional filename security check
|
||||
if ".." in original_filename:
|
||||
print(f"⚠️ Security: Insecure filename: {original_filename}")
|
||||
return {"ui": {"files": []}}
|
||||
|
||||
results = []
|
||||
|
||||
# Check if file is in output folder, if not, symlink it there
|
||||
try:
|
||||
if file_path.startswith(self.output_dir):
|
||||
# File is already in output directory - use as is
|
||||
relative_path = os.path.relpath(file_path, self.output_dir)
|
||||
path_parts = relative_path.split(os.sep)
|
||||
|
||||
if len(path_parts) > 1:
|
||||
subfolder = os.sep.join(path_parts[:-1])
|
||||
else:
|
||||
subfolder = ""
|
||||
|
||||
filename = path_parts[-1]
|
||||
file_type = self.type
|
||||
else:
|
||||
# File is not in output folder - symlink it to output/temp
|
||||
print(
|
||||
f"File is not in output folder, symlinking to output/temp: {file_path}"
|
||||
)
|
||||
output_temp_dir = os.path.join(self.output_dir, "temp")
|
||||
if not os.path.exists(output_temp_dir):
|
||||
os.makedirs(output_temp_dir)
|
||||
|
||||
# Use the existing filename but with UUID prefix to avoid conflicts
|
||||
file_ext = os.path.splitext(original_filename)[1]
|
||||
temp_filename = f"{uuid.uuid4()}{file_ext}"
|
||||
temp_path = os.path.join(output_temp_dir, temp_filename)
|
||||
|
||||
# Create symlink to file in output/temp directory where upload system expects it
|
||||
try:
|
||||
# Remove existing symlink if it exists
|
||||
if os.path.exists(temp_path):
|
||||
os.remove(temp_path)
|
||||
os.symlink(file_path, temp_path)
|
||||
print(f"File symlinked to output/temp: {temp_path} -> {file_path}")
|
||||
except OSError as e:
|
||||
# Fall back to copying if symlink fails
|
||||
print(f"Symlink failed ({e}), falling back to copy")
|
||||
shutil.copy2(file_path, temp_path)
|
||||
print(f"File copied to output/temp: {temp_path}")
|
||||
|
||||
# Use output/temp directory structure for upload
|
||||
subfolder = "temp"
|
||||
filename = temp_filename
|
||||
file_type = self.type
|
||||
|
||||
results.append(
|
||||
{
|
||||
"filename": filename,
|
||||
"subfolder": subfolder,
|
||||
"type": file_type,
|
||||
"output_id": output_id,
|
||||
}
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
print(f"⚠️ Error processing file path: {str(e)}")
|
||||
return {"ui": {"files": []}}
|
||||
|
||||
# Determine the appropriate UI key based on file type
|
||||
file_ext = file_extension.lower()
|
||||
if file_ext in [".png", ".jpg", ".jpeg", ".webp", ".gif", ".bmp", ".tiff"]:
|
||||
ui_key = "images"
|
||||
elif file_ext in [".mp3", ".wav", ".flac", ".aac", ".ogg"]:
|
||||
ui_key = "audio"
|
||||
elif file_ext in [".txt", ".json", ".md", ".csv"]:
|
||||
ui_key = "text_file"
|
||||
elif file_ext in [".exr", ".hdr"]:
|
||||
ui_key = "images" # EXR files are still images
|
||||
elif file_ext in [".zip", ".psb", ".psd"]:
|
||||
ui_key = "files" # Archives and Photoshop project files
|
||||
else:
|
||||
ui_key = "files" # Generic files
|
||||
|
||||
return {"ui": {ui_key: results}}
|
||||
|
||||
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
"ComfyDeployOutputFile": ComfyDeployOutputFile,
|
||||
}
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"ComfyDeployOutputFile": "File Output (ComfyDeploy)",
|
||||
}
|
||||
@@ -516,6 +516,9 @@ def apply_inputs_to_workflow(workflow_api: Any, inputs: Any, sid: str = None):
|
||||
if value["class_type"] == "ComfyUIDeployExternalEXR":
|
||||
value["inputs"]["exr_file"] = new_value
|
||||
|
||||
if value["class_type"] == "ComfyUIDeployExternalFile":
|
||||
value["inputs"]["file_url"] = new_value
|
||||
|
||||
if value["class_type"] == "ComfyUIDeployExternalSeed":
|
||||
logger.info(
|
||||
f"Applied random seed {new_value} to {value['class_type']}"
|
||||
@@ -896,6 +899,10 @@ async def upload_file_endpoint(request):
|
||||
file_type = "image/png"
|
||||
elif file_extension == ".webp":
|
||||
file_type = "image/webp"
|
||||
elif file_extension == ".zip":
|
||||
file_type = "application/zip"
|
||||
elif file_extension in [".psd", ".psb"]:
|
||||
file_type = "image/vnd.adobe.photoshop"
|
||||
else:
|
||||
file_type = (
|
||||
"application/octet-stream" # Default to binary file type if unknown
|
||||
@@ -2098,6 +2105,10 @@ async def handle_upload(
|
||||
file_type = "image/png"
|
||||
elif file_extension == ".webp":
|
||||
file_type = "image/webp"
|
||||
elif file_extension == ".zip":
|
||||
file_type = "application/zip"
|
||||
elif file_extension in [".psd", ".psb"]:
|
||||
file_type = "image/vnd.adobe.photoshop"
|
||||
|
||||
upload_tasks.append(
|
||||
upload_file(
|
||||
@@ -2563,6 +2574,10 @@ class UploadQueue:
|
||||
content_type = "image/webp"
|
||||
elif file_extension == ".gif":
|
||||
content_type = "image/gif"
|
||||
elif file_extension == ".zip":
|
||||
content_type = "application/zip"
|
||||
elif file_extension in [".psd", ".psb"]:
|
||||
content_type = "image/vnd.adobe.photoshop"
|
||||
else:
|
||||
content_type = file_info.get("content_type", "application/octet-stream")
|
||||
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
[project]
|
||||
name = "comfyui-deploy"
|
||||
description = "Open source comfyui deployment platform, a vercel for generative workflow infra."
|
||||
version = "2.3.6"
|
||||
version = "2.3.7"
|
||||
license = { file = "LICENSE" }
|
||||
dependencies = ["aiofiles", "pydantic", "opencv-python", "imageio-ffmpeg", "tabulate", "brotli"]
|
||||
|
||||
|
||||
Reference in New Issue
Block a user