Merge pull request #11 from fchangjun/codex/url-download-subdir
Codex/url download subdir
This commit is contained in:
@@ -9,7 +9,7 @@
|
||||
### Overview
|
||||
|
||||
Baidu Meux ComfyTools is a collection of custom nodes for ComfyUI used by the Baidu Meux asset platform.
|
||||
Current version: **1.7.0**
|
||||
Current version: **1.7.1**
|
||||
|
||||
Main nodes:
|
||||
|
||||
@@ -38,6 +38,9 @@ Workflow-compatible aliases:
|
||||
|
||||
### Changelog
|
||||
|
||||
- **v1.7.1**
|
||||
- Updated URL persistence so downloaded files are stored under `ComfyUI/input/meux_downloads`.
|
||||
- Added `text_align` and `vertical_align` controls to `MeuxArtisticTextPreview`.
|
||||
- **v1.7.0**
|
||||
- Added `MeuxArtisticTextPreview` for transparent text preview rendering with fill, outline, spacing, mask output, and preset-based effects.
|
||||
- Added a project-level `fonts/` directory and made it the first font lookup location for the preview node.
|
||||
@@ -93,6 +96,7 @@ Workflow-compatible aliases:
|
||||
|
||||
- `source_type = local`: select a file from `ComfyUI/input`
|
||||
- `source_type = url`: download from an HTTP/HTTPS URL
|
||||
- Downloaded URL files are persisted under `ComfyUI/input/meux_downloads` when `persist_to_input = true`
|
||||
- Outputs `IMAGE` and the derived mask from alpha or grayscale when present
|
||||
|
||||
#### MeuxMaskLoader
|
||||
@@ -252,7 +256,7 @@ Baidu_Meux_ComfyTools/
|
||||
### 概述
|
||||
|
||||
Baidu Meux ComfyTools 是一组用于 ComfyUI 的自定义节点,服务于百度 Meux 资产平台相关工作流。
|
||||
当前版本:**1.7.0**
|
||||
当前版本:**1.7.1**
|
||||
|
||||
主要节点:
|
||||
|
||||
@@ -281,6 +285,9 @@ Baidu Meux ComfyTools 是一组用于 ComfyUI 的自定义节点,服务于百
|
||||
|
||||
### 更新日志
|
||||
|
||||
- **v1.7.1**
|
||||
- 调整 URL 落盘位置,下载文件现在保存到 `ComfyUI/input/meux_downloads`
|
||||
- 为 `MeuxArtisticTextPreview` 新增 `text_align` 和 `vertical_align` 对齐控制
|
||||
- **v1.7.0**
|
||||
- 新增 `MeuxArtisticTextPreview`,用于透明背景文字预览,支持填充、描边、字距、遮罩输出和预设效果。
|
||||
- 新增项目级 `fonts/` 目录,并作为预览节点的第一字体读取位置。
|
||||
@@ -336,6 +343,7 @@ Baidu Meux ComfyTools 是一组用于 ComfyUI 的自定义节点,服务于百
|
||||
|
||||
- `source_type = local`:从 `ComfyUI/input` 选择本地文件
|
||||
- `source_type = url`:运行时下载 HTTP/HTTPS 图片
|
||||
- `persist_to_input = true` 时,URL 下载文件会保存到 `ComfyUI/input/meux_downloads`
|
||||
- 输出 `IMAGE`,以及图片自带 alpha/灰度派生的 `MASK`
|
||||
|
||||
#### MeuxMaskLoader
|
||||
|
||||
+1
-1
@@ -62,7 +62,7 @@ NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
}
|
||||
|
||||
# 可选:添加版本和作者信息
|
||||
__version__ = "1.7.0"
|
||||
__version__ = "1.7.1"
|
||||
__author__ = "fangchangjun"
|
||||
|
||||
# 调试信息 - 可以帮助确认导入是否成功
|
||||
|
||||
@@ -41,6 +41,8 @@ class MeuxArtisticTextPreview:
|
||||
"font_size": ("INT", {"default": 128, "min": 8, "max": 1024, "step": 1}),
|
||||
"char_spacing": ("INT", {"default": 0, "min": -20, "max": 200, "step": 1}),
|
||||
"line_spacing": ("INT", {"default": 0, "min": -512, "max": 512, "step": 1}),
|
||||
"text_align": (["left", "center", "right"], {"default": "center"}),
|
||||
"vertical_align": (["top", "middle", "bottom"], {"default": "middle"}),
|
||||
"bold": ("BOOLEAN", {"default": False}),
|
||||
"italic": ("BOOLEAN", {"default": False}),
|
||||
"background_color": ("STRING", {"default": "#000000"}),
|
||||
@@ -83,6 +85,8 @@ class MeuxArtisticTextPreview:
|
||||
font_size,
|
||||
char_spacing,
|
||||
line_spacing,
|
||||
text_align,
|
||||
vertical_align,
|
||||
bold,
|
||||
italic,
|
||||
background_color,
|
||||
@@ -132,6 +136,8 @@ class MeuxArtisticTextPreview:
|
||||
font=font,
|
||||
char_spacing=char_spacing,
|
||||
line_spacing=line_spacing,
|
||||
text_align=text_align,
|
||||
vertical_align=vertical_align,
|
||||
safe_area=safe_area,
|
||||
fake_bold=fake_bold,
|
||||
)
|
||||
@@ -299,18 +305,17 @@ class MeuxArtisticTextPreview:
|
||||
return int(line_spacing)
|
||||
return max(6, font.size // 5 if hasattr(font, "size") else 6)
|
||||
|
||||
def _render_text_mask(self, text, width, height, font, char_spacing, line_spacing, safe_area, fake_bold):
|
||||
def _render_text_mask(
|
||||
self, text, width, height, font, char_spacing, line_spacing, text_align, vertical_align, safe_area, fake_bold
|
||||
):
|
||||
mask_image = Image.new("L", (width, height), 0)
|
||||
draw = ImageDraw.Draw(mask_image)
|
||||
|
||||
line_spacing = self._resolve_line_spacing(font, line_spacing)
|
||||
block_width, block_height = self._text_block_size(font, text, char_spacing, line_spacing)
|
||||
_, block_height = self._text_block_size(font, text, char_spacing, line_spacing)
|
||||
|
||||
left, top, right, bottom = safe_area
|
||||
safe_width = right - left
|
||||
safe_height = bottom - top
|
||||
start_x = left + (safe_width - block_width) / 2
|
||||
start_y = top + (safe_height - block_height) / 2
|
||||
start_y = self._block_start_y(block_height, vertical_align, top, bottom)
|
||||
|
||||
offsets = ((0, 0), (1, 0), (0, 1), (1, 1)) if fake_bold else ((0, 0),)
|
||||
lines = text.splitlines() or [text]
|
||||
@@ -319,7 +324,8 @@ class MeuxArtisticTextPreview:
|
||||
current_y = start_y
|
||||
|
||||
for line in lines:
|
||||
current_x = start_x
|
||||
line_width = self._line_width(font, line, char_spacing)
|
||||
current_x = self._line_start_x(line_width, text_align, left, right)
|
||||
for index, char in enumerate(line):
|
||||
for dx, dy in offsets:
|
||||
draw.text((current_x + dx, current_y + dy), char, fill=255, font=font)
|
||||
@@ -330,6 +336,22 @@ class MeuxArtisticTextPreview:
|
||||
|
||||
return mask_image
|
||||
|
||||
def _line_start_x(self, line_width, text_align, left, right):
|
||||
safe_width = right - left
|
||||
if text_align == "left":
|
||||
return left
|
||||
if text_align == "right":
|
||||
return right - line_width
|
||||
return left + (safe_width - line_width) / 2
|
||||
|
||||
def _block_start_y(self, block_height, vertical_align, top, bottom):
|
||||
safe_height = bottom - top
|
||||
if vertical_align == "top":
|
||||
return top
|
||||
if vertical_align == "bottom":
|
||||
return bottom - block_height
|
||||
return top + (safe_height - block_height) / 2
|
||||
|
||||
def _apply_fake_italic(self, mask_image, shear=0.25):
|
||||
bbox = mask_image.getbbox()
|
||||
if bbox is None:
|
||||
|
||||
+32
-21
@@ -17,6 +17,8 @@ class ImageLoader:
|
||||
Hybrid image loader that mirrors ComfyUI's Load Image node while adding URL download support.
|
||||
"""
|
||||
|
||||
URL_DOWNLOAD_SUBDIR = "meux_downloads"
|
||||
|
||||
RETURN_TYPES = ("IMAGE", "MASK")
|
||||
RETURN_NAMES = ("image", "mask")
|
||||
FUNCTION = "load_image"
|
||||
@@ -180,24 +182,21 @@ class ImageLoader:
|
||||
return mask
|
||||
|
||||
def _persist_image(self, image: Image.Image, filename_hint: str, url: str, overwrite: bool):
|
||||
try:
|
||||
input_dir = folder_paths.get_input_directory()
|
||||
except KeyError:
|
||||
raise ValueError("未检测到 ComfyUI 的 input 目录,请在根目录创建 input 文件夹后重启。")
|
||||
|
||||
input_dir = self._get_input_directory()
|
||||
persist_dir = self._get_url_persist_directory(create=True)
|
||||
extension = self._infer_extension(image, url)
|
||||
filename = self._build_filename(filename_hint or os.path.basename(urlparse(url).path), extension, url)
|
||||
full_path = os.path.abspath(os.path.join(input_dir, filename))
|
||||
input_dir_abs = os.path.abspath(input_dir)
|
||||
if not (full_path.startswith(input_dir_abs + os.sep) or full_path == input_dir_abs):
|
||||
raise ValueError("无法写入到 input 目录之外。")
|
||||
filename = self._build_cached_filename(filename_hint, extension, url)
|
||||
full_path = os.path.abspath(os.path.join(persist_dir, filename))
|
||||
persist_dir_abs = os.path.abspath(persist_dir)
|
||||
if not (full_path.startswith(persist_dir_abs + os.sep) or full_path == persist_dir_abs):
|
||||
raise ValueError("无法写入到 URL 下载缓存目录之外。")
|
||||
|
||||
if os.path.exists(full_path) and not overwrite:
|
||||
stem, ext = os.path.splitext(filename)
|
||||
counter = 1
|
||||
while True:
|
||||
candidate = f"{stem}_{counter}{ext}"
|
||||
candidate_path = os.path.join(input_dir, candidate)
|
||||
candidate_path = os.path.join(persist_dir, candidate)
|
||||
if not os.path.exists(candidate_path):
|
||||
full_path = candidate_path
|
||||
break
|
||||
@@ -210,19 +209,31 @@ class ImageLoader:
|
||||
else:
|
||||
image.save(full_path)
|
||||
print(f"[MeuxImageLoader] 已保存到 {os.path.relpath(full_path, input_dir)}")
|
||||
return full_path
|
||||
|
||||
def _build_filename(self, hint: str, extension: str, url: str) -> str:
|
||||
sanitized = self._sanitize_filename(hint) if hint else ""
|
||||
if sanitized.endswith(extension):
|
||||
base = sanitized[: -len(extension)]
|
||||
else:
|
||||
base = sanitized or ""
|
||||
def _build_cached_filename(self, filename_hint: str, extension: str, url: str) -> str:
|
||||
digest = hashlib.sha1(url.encode("utf-8")).hexdigest()[:16]
|
||||
path_name = os.path.basename(urlparse(url).path)
|
||||
label_source = filename_hint or path_name or ""
|
||||
label = self._sanitize_filename(label_source)
|
||||
if label.endswith(extension):
|
||||
label = label[: -len(extension)]
|
||||
if label:
|
||||
return f"meux_url_{digest}__{label}{extension}"
|
||||
return f"meux_url_{digest}{extension}"
|
||||
|
||||
if not base:
|
||||
digest = hashlib.sha1(url.encode("utf-8")).hexdigest()[:12]
|
||||
base = f"meux_url_{digest}"
|
||||
def _get_input_directory(self) -> str:
|
||||
try:
|
||||
return folder_paths.get_input_directory()
|
||||
except KeyError:
|
||||
raise ValueError("未检测到 ComfyUI 的 input 目录,请在根目录创建 input 文件夹后重启。")
|
||||
|
||||
return f"{base}{extension}"
|
||||
def _get_url_persist_directory(self, create: bool) -> str:
|
||||
input_dir = self._get_input_directory()
|
||||
persist_dir = os.path.join(input_dir, self.URL_DOWNLOAD_SUBDIR)
|
||||
if create:
|
||||
os.makedirs(persist_dir, exist_ok=True)
|
||||
return persist_dir
|
||||
|
||||
def _infer_extension(self, image: Image.Image, url: str) -> str:
|
||||
parsed = urlparse(url)
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
[project]
|
||||
name = "baidu_meux_comfy_tools"
|
||||
description = "Baidu Meux ComfyTools is a collection of custom nodes that streamline common workflow chores in ComfyUI for the Baidu Meux asset platform.."
|
||||
version = "1.7.0"
|
||||
version = "1.7.1"
|
||||
license = {file = "LICENSE"}
|
||||
# classifiers = [
|
||||
# # For OS-independent nodes (works on all operating systems)
|
||||
|
||||
Reference in New Issue
Block a user