From 2d5775bfe92bcc4b00cd7c397fbae3f5bbd04cfa Mon Sep 17 00:00:00 2001 From: Asidert <612boris40@gmail.com> Date: Mon, 22 Dec 2025 13:11:31 +0400 Subject: [PATCH] Initial commit --- .gitattributes | 2 + .gitignore | 114 ++++++++++++++++++++++++++++++++++++++++++++++++ LICENSE | 21 +++++++++ MANIFEST.in | 9 ++++ README.md | 2 + __init__.py | 13 ++++++ pyproject.toml | 27 ++++++++++++ src/__init__.py | 0 src/nodes.py | 84 +++++++++++++++++++++++++++++++++++ 9 files changed, 272 insertions(+) create mode 100644 .gitattributes create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 MANIFEST.in create mode 100644 README.md create mode 100644 __init__.py create mode 100644 pyproject.toml create mode 100644 src/__init__.py create mode 100644 src/nodes.py diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..dfe0770 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +# Auto detect text files and perform LF normalization +* text=auto diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0fedfe7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,114 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# OSX useful to ignore +*.DS_Store +.AppleDouble +.LSOverride + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +# C extensions +*.so + +# Distribution / packaging +.Python +env/ +venv/ +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +*.egg-info/ +.installed.cfg +*.egg + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*,cover +.hypothesis/ +.pytest_cache/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log + +# Sphinx documentation +docs/_build/ + +# IntelliJ Idea +.idea +*.iml +*.ipr +*.iws + +# PyBuilder +target/ + +# Cookiecutter +output/ +python_boilerplate/ +cookiecutter-pypackage-env/ + +# vscode settings +.history/ +*.code-workspace + +# Frontend extension +node_modules/ +.env +.env.local +.env.development.local +.env.test.local +.env.production.local +npm-debug.log* +yarn-debug.log* +yarn-error.log* +node.zip +.vscode/ +.claude/ diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..6b2e400 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 Asidert + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..6514960 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,9 @@ +include LICENSE +include README.md + +recursive-exclude * __pycache__ +recursive-exclude * *.py[co] + +recursive-include docs *.rst conf.py Makefile make.bat *.jpg *.png *.gif + + diff --git a/README.md b/README.md new file mode 100644 index 0000000..5e92d70 --- /dev/null +++ b/README.md @@ -0,0 +1,2 @@ +# ComfyUI_Base64Images +A set of base64 input and output nodes for a custom API using ComfyUI diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..a82c916 --- /dev/null +++ b/__init__.py @@ -0,0 +1,13 @@ +"""Top-level package for comfyui_base64images.""" + +from .src.nodes import NODE_CLASS_MAPPINGS +from .src.nodes import NODE_DISPLAY_NAME_MAPPINGS + +__all__ = [ + "NODE_CLASS_MAPPINGS", + "NODE_DISPLAY_NAME_MAPPINGS", +] + +__author__ = """Boris Zyrianov""" +__email__ = "612boris40@gmail.com" +__version__ = "1.0.0" diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..994cfce --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,27 @@ +[build-system] +requires = ["setuptools>=70.0"] +build-backend = "setuptools.build_meta" + +[project] +name = "comfyui_base64images" +version = "1.0.0" +description = "A set of base64 input and output nodes for a custom API using ComfyUI" +authors = [{name = "Boris Zyrianov", email = "612boris40@gmail.com"}] +readme = "README.md" +license = {text = "MIT license"} +requires-python = ">=3.10" +classifiers = [] +dependencies = [] + +[project.urls] +Repository = "https://github.com/Asidert/comfyui_base64images" +BugTracker = "https://github.com/Asidert/comfyui_base64images/issues" + +[tool.comfy] +PublisherId = "Asidert" +DisplayName = "ComfyUI_Base64Images" +Icon = "" +Tags = [] +Repository = "https://github.com/Asidert/comfyui_base64images" + +includes = [] diff --git a/src/__init__.py b/src/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/nodes.py b/src/nodes.py new file mode 100644 index 0000000..7d0aa97 --- /dev/null +++ b/src/nodes.py @@ -0,0 +1,84 @@ +import cv2 +import base64 +import numpy as np + +from PIL import Image +from io import BytesIO +from torch import Tensor + + +class LoadImageFromBase64: + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "data": ("STRING", {"default": ""}), + } + } + + RETURN_TYPES = ("IMAGE", "MASK") + FUNCTION = "main" + CATEGORY = "image" + + def convert_color(self, image: np.ndarray) -> np.ndarray: + code = cv2.COLOR_BGRA2RGB if image.ndim > 2 and image.shape[2] >= 4 else cv2.COLOR_BGR2RGB + return cv2.cvtColor(image, code) + + def main(self, data: str) -> tuple[Tensor, Tensor]: + nparr = np.frombuffer(base64.b64decode(data), np.uint8) + + result = cv2.imdecode(nparr, cv2.IMREAD_UNCHANGED) + channels = cv2.split(result) + if len(channels) > 3: + mask = channels[3].astype(np.float32) / 255.0 + mask = torch.from_numpy(mask) + else: + mask = torch.ones(channels[0].shape, dtype=torch.float32, device="cpu") + + result = self.convert_color(result) + result = result.astype(np.float32) / 255.0 + image = torch.from_numpy(result)[None,] + return image, mask.unsqueeze(0) + + +class ConvertImageToBase64: + def __init__(self): + self.type = "output" + + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "images": ("IMAGE", {}), + } + } + + RETURN_TYPES = () + FUNCTION = "main" + OUTPUT_NODE = True + CATEGORY = "image" + + def main(self, images: Tensor) -> list[dict[str, str]]: + results: list[dict[str, str]] = list() + + for image in images: + buffer = BytesIO() + image_array = 255.0 * image.cpu().numpy() + Image.fromarray(np.clip(image_array, 0, 255).astype(np.uint8)).save(buffer, format="PNG") + result = base64.b64encode(buffer.getbuffer()).decode("utf-8") + + results.append({"base64": result}) + + return { "ui": { "images": results } } + + +NODE_CLASS_MAPPINGS = { + 'LoadImageFromBase64': LoadImageFromBase64, + 'ConvertImageToBase64': ConvertImageToBase64, +} + + +NODE_DISPLAY_NAME_MAPPINGS = { + "LoadImageFromBase64": "Load Image From Base64", + "ConvertImageToBase64": "Convert Image To Base64", +}