Add files via upload
This commit is contained in:
@@ -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.
|
||||
@@ -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
|
||||
@@ -0,0 +1,9 @@
|
||||
from .nodes.audio_export import AudioExport
|
||||
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
"AudioExport": AudioExport
|
||||
}
|
||||
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"AudioExport": "Audio Export"
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"name": "ComfyUI-LikeSpiderAI-UI",
|
||||
"author": "Pigidiy",
|
||||
"version": "1.0.0",
|
||||
"description": "Declarative UI Framework for ComfyUI Nodes",
|
||||
"license": "MIT"
|
||||
}
|
||||
@@ -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,)
|
||||
+95
@@ -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
|
||||
Reference in New Issue
Block a user