From 0df9923ddd98561beefc0b14ef97b3c72b3d2148 Mon Sep 17 00:00:00 2001 From: Pigidiy Date: Fri, 6 Jun 2025 01:20:03 +0600 Subject: [PATCH] Add files via upload --- LICENSE | 21 ++++++++++ README.md | 30 ++++++++++++++ __init__.py | 9 ++++ info.json | 7 ++++ nodes/audio_export.py | 19 +++++++++ ui_base.py | 95 +++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 181 insertions(+) create mode 100644 LICENSE create mode 100644 README.md create mode 100644 __init__.py create mode 100644 info.json create mode 100644 nodes/audio_export.py create mode 100644 ui_base.py diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..64b26ad --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 Pigidiy + +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/README.md b/README.md new file mode 100644 index 0000000..1fd2132 --- /dev/null +++ b/README.md @@ -0,0 +1,30 @@ +# ComfyUI-LikeSpiderAI-UI + +**Declarative UI Framework for ComfyUI Nodes** + +This extension introduces a base class for creating custom ComfyUI nodes using a declarative structure. + +Instead of manually defining INPUT_TYPES and UI logic, you can focus on your node's functionality using an intuitive `UI_CONFIG` object. + +--- + +## 📦 Features + +- Minimalistic base class: `LikeSpiderUINode` +- Auto-generated UI based on simple schema +- Clean structure for rapid development +- Designed for scalability and extensions + +--- + +## 🚀 Example Node + +`Audio Export` — a sample node that saves audio files as `.mp3`, `.wav`, or `.flac`, with bitrate and filename options. + +Source: [`nodes/audio_export.py`](nodes/audio_export.py) + +--- + +## ⚖️ License + +MIT © Pigidiy diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..ee2519e --- /dev/null +++ b/__init__.py @@ -0,0 +1,9 @@ +from .nodes.audio_export import AudioExport + +NODE_CLASS_MAPPINGS = { + "AudioExport": AudioExport +} + +NODE_DISPLAY_NAME_MAPPINGS = { + "AudioExport": "Audio Export" +} diff --git a/info.json b/info.json new file mode 100644 index 0000000..2f43982 --- /dev/null +++ b/info.json @@ -0,0 +1,7 @@ +{ + "name": "ComfyUI-LikeSpiderAI-UI", + "author": "Pigidiy", + "version": "1.0.0", + "description": "Declarative UI Framework for ComfyUI Nodes", + "license": "MIT" +} diff --git a/nodes/audio_export.py b/nodes/audio_export.py new file mode 100644 index 0000000..411a80c --- /dev/null +++ b/nodes/audio_export.py @@ -0,0 +1,19 @@ +from ..ui_base import LikeSpiderUINode + +class AudioExport(LikeSpiderUINode): + """ + Save audio with custom filename, bitrate and format (.mp3, .wav, .flac). + """ + + UI_CONFIG = { + "inputs": [ + {"name": "filename", "type": "string", "default": "track"}, + {"name": "bitrate", "type": "select", "options": [64, 128, 192, 256, 320], "default": 192}, + {"name": "format", "type": "select", "options": ["mp3", "wav", "flac"], "default": "mp3"}, + {"name": "audio", "type": "audio"} + ] + } + + def run(self, filename, bitrate, format, audio): + self.save_audio_with_ffmpeg(audio_data=audio, filename_base=filename, format=format, bitrate=bitrate) + return (audio,) diff --git a/ui_base.py b/ui_base.py new file mode 100644 index 0000000..7e7162e --- /dev/null +++ b/ui_base.py @@ -0,0 +1,95 @@ +import os +import shutil +import subprocess +from pathlib import Path +import numpy as np +import torch +import folder_paths +from scipy.io.wavfile import write + +class LikeSpiderUINode: + """ + Base class for declarative UI-driven ComfyUI nodes. + Extend this class and define `UI_CONFIG` and `run(...)`. + """ + + @classmethod + def INPUT_TYPES(cls): + config = cls.UI_CONFIG + required = {} + + for field in config["inputs"]: + name = field["name"] + type_ = field["type"] + default = field.get("default") + + if type_ == "string": + required[name] = ("STRING", {"default": default or ""}) + elif type_ == "select": + required[name] = (field["options"], {"default": default}) + elif type_ == "audio": + required[name] = ("AUDIO",) + else: + raise ValueError(f"Unsupported field type: {type_}") + + return {"required": required} + + RETURN_TYPES = ("AUDIO",) + RETURN_NAMES = ("audio",) + FUNCTION = "interface_entry" + OUTPUT_NODE = True + CATEGORY = "LikeSpiderAI" + + def interface_entry(self, **kwargs): + return self.run(**kwargs) + + def save_audio_with_ffmpeg(self, audio_data, filename_base, format="mp3", bitrate=192): + samples = audio_data["waveform"] + if isinstance(samples, torch.Tensor): + samples = samples.detach().cpu().numpy() + if samples.ndim == 3 and samples.shape[0] == 1: + samples = samples[0] + + max_val = np.max(np.abs(samples)) + if max_val > 1.0: + samples = samples / max_val + samples = np.clip(samples, -1.0, 1.0) + audio_int16 = (samples * 32767).astype(np.int16) + + if audio_int16.ndim == 2 and audio_int16.shape[0] in [1, 2]: + wav_data = audio_int16.T + elif audio_int16.ndim == 1: + wav_data = audio_int16 + else: + raise Exception(f"❌ Unsupported audio shape: {audio_int16.shape}") + + output_dir = Path(folder_paths.get_output_directory()) / "audio" + output_dir.mkdir(parents=True, exist_ok=True) + + existing = sorted(output_dir.glob(f"{filename_base}_*.{format}")) + index = len(existing) + file_path = output_dir / f"{filename_base}_{index:05d}.{format}" + + temp_wav = output_dir / "temp_input.wav" + write(temp_wav, 44100, wav_data) + + if format == "mp3": + subprocess.run([ + "ffmpeg", "-y", "-i", str(temp_wav), + "-codec:a", "libmp3lame", "-b:a", f"{bitrate}k", + str(file_path) + ], check=True) + elif format == "wav": + shutil.copy(temp_wav, file_path) + elif format == "flac": + subprocess.run([ + "ffmpeg", "-y", "-i", str(temp_wav), + "-codec:a", "flac", + str(file_path) + ], check=True) + else: + raise Exception(f"Unsupported format: {format}") + + os.remove(temp_wav) + print(f"[LikeSpiderUINode] ✅ Saved: {file_path}") + return file_path