228 lines
8.2 KiB
Markdown
228 lines
8.2 KiB
Markdown
|
|
# Documentation
|
|
- Class name: SaltOutput
|
|
- Category: SALT/IO
|
|
- Output node: True
|
|
- Repo Ref: https://github.com/comfyanonymous/ComfyUI
|
|
|
|
SaltOutput节点被设计用于处理各种类型的输出数据,包括图像、音频和文本。它能够根据指定的输出类型适当地格式化和保存这些数据。该节点支持广泛的文件格式,并且能够生成复杂的UI结构来有效地表示输出数据。
|
|
|
|
# Input types
|
|
## Required
|
|
- output_name
|
|
- 指定输出的名称,用作生成文件名和已保存输出文件中标识符的基础。
|
|
- Comfy dtype: STRING
|
|
- Python dtype: str
|
|
- output_desc
|
|
- 为输出提供描述,可用于元数据或UI显示目的。
|
|
- Comfy dtype: STRING
|
|
- Python dtype: str
|
|
- output_type
|
|
- 确定输出文件的格式(如PNG、JPEG、MP3、WAV、STRING),影响输出数据的处理和保存方式。
|
|
- Comfy dtype: COMBO[STRING]
|
|
- Python dtype: str
|
|
- output_data
|
|
- 要输出的实际数据,其类型可能大不相同(例如,音频为bytes,图像为torch.Tensor,文本输出为string)。
|
|
- Comfy dtype: *
|
|
- Python dtype: Union[bytes, torch.Tensor, str]
|
|
## Optional
|
|
- video_audio
|
|
- 视频输出的可选音频数据,指定要包含在视频文件中的音轨。
|
|
- Comfy dtype: AUDIO
|
|
- Python dtype: bytes
|
|
- animation_fps
|
|
- 指定动画输出的帧率,允许控制动画速度。
|
|
- Comfy dtype: INT
|
|
- Python dtype: int
|
|
- animation_quality
|
|
- 定义动画的质量级别(DEFAULT或HIGH),影响输出文件的视觉保真度。
|
|
- Comfy dtype: COMBO[STRING]
|
|
- Python dtype: str
|
|
|
|
# Output types
|
|
- ui
|
|
- 生成一个复杂的UI结构来表示输出数据,包括支持的文件类型的元数据和预览。
|
|
|
|
|
|
## Usage tips
|
|
- Infra type: `CPU`
|
|
- Common nodes: unknown
|
|
|
|
|
|
## Source code
|
|
```python
|
|
class SaltOutput:
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"output_name": ("STRING", {}),
|
|
"output_desc": ("STRING", {}),
|
|
"output_type": (
|
|
["PNG", "JPEG", "GIF", "WEBP", "AVI", "MP4", "WEBM", "MP3", "WAV", "STRING"],
|
|
),
|
|
"output_data": (WILDCARD, {}),
|
|
},
|
|
"optional": {
|
|
"video_audio": ("AUDIO", {}),
|
|
"animation_fps": ("INT", {"min": 1, "max": 60, "default": 8}),
|
|
"animation_quality": (["DEFAULT", "HIGH"],),
|
|
},
|
|
"hidden": {"unique_id": "UNIQUE_ID", "output_subdir": None},
|
|
}
|
|
|
|
OUTPUT_NODE = True
|
|
RETURN_TYPES = ()
|
|
|
|
FUNCTION = "output"
|
|
CATEGORY = "SALT/IO"
|
|
|
|
def output(
|
|
self,
|
|
output_name,
|
|
output_desc,
|
|
output_type,
|
|
output_data,
|
|
video_audio=None,
|
|
animation_fps=8,
|
|
animation_quality="DEFAULT",
|
|
unique_id=0,
|
|
output_subdir=None
|
|
):
|
|
is_asset = False
|
|
asset_id = str(uuid.uuid4())
|
|
|
|
# Determine if valid type
|
|
if not isinstance(output_data, torch.Tensor) and not isinstance(output_data, str) and not isinstance(output_data, bytes) and not is_lambda(output_data):
|
|
raise ValueError(
|
|
f"Unsupported output_data supplied `{str(type(output_data).__name__)}`. Please provide `IMAGE` (torch.Tensor), `STRING` (str), or `AUDIO` (bytes) input."
|
|
)
|
|
|
|
# Support VHS audio
|
|
if output_type in ["AVI", "MP4", "WEBM", "MP3", "WAV"]:
|
|
if video_audio is not None and is_lambda(video_audio):
|
|
video_audio = video_audio()
|
|
if is_lambda(output_data):
|
|
output_data = output_data()
|
|
|
|
if video_audio and not isinstance(video_audio, bytes):
|
|
raise ValueError(
|
|
f"Unsupported video_audio supplied `{str(type(output_data).__name__)}. Please provide `AUDIO` (bytes)"
|
|
)
|
|
|
|
# Is asset? I may have misunderstood this part
|
|
if output_type in ["GIF", "WEBP", "AVI", "MP4", "WEBM", "MP3", "WAV"]:
|
|
is_asset = True
|
|
|
|
# Determine output name, and sanitize if input (for filesystem)
|
|
if output_name.strip() == "":
|
|
output_name = "output_" + str(unique_id)
|
|
else:
|
|
output_name = sanitize_filename(output_name)
|
|
|
|
# Create output dir based on uuid4
|
|
subfolder = os.path.join(output_subdir or '', asset_id)
|
|
output_path = os.path.join(folder_paths.get_output_directory(), subfolder)
|
|
|
|
os.makedirs(output_path, exist_ok=True)
|
|
if not os.path.exists(output_path):
|
|
print(f"[SALT] Unable to create output directory `{output_path}`")
|
|
|
|
results = []
|
|
if output_type in ("PNG", "JPEG") and isinstance(output_data, torch.Tensor):
|
|
# Save all images in the tensor batch as specified by output_type
|
|
try:
|
|
for index, img in enumerate(output_data):
|
|
pil_image = tensor2pil(img)
|
|
file_prefix = output_name.strip().replace(" ", "_")
|
|
file_ext = f".{output_type.lower()}"
|
|
filename = f"{file_prefix}_{index:04d}{file_ext}"
|
|
image_path = os.path.join(output_path, filename)
|
|
pil_image.save(image_path, output_type)
|
|
results.append({
|
|
"filename": filename,
|
|
"subfolder": subfolder,
|
|
"type": "output"
|
|
})
|
|
if os.path.exists(image_path):
|
|
print(f"[SALT] Saved image to `{image_path}`")
|
|
else:
|
|
print(f"[SALT] Unable to save image to `{image_path}`")
|
|
|
|
except Exception as e:
|
|
raise e
|
|
|
|
if output_type in ["GIF", "WEBP", "AVI", "MP4", "WEBM"] and isinstance(output_data, torch.Tensor):
|
|
# Save animation file
|
|
filename = os.path.join(output_path, f"{output_name}.{output_type.lower()}")
|
|
animator = ImageAnimator(
|
|
output_data, fps=int(animation_fps), quality=animation_quality
|
|
)
|
|
animator.save_animation(filename, format=output_type, audio=video_audio)
|
|
results.append({
|
|
"filename": os.path.basename(filename),
|
|
"subfolder": subfolder,
|
|
"type": "output"
|
|
})
|
|
if os.path.exists(filename):
|
|
print(f"[SALT] Saved file to `{filename}`")
|
|
else:
|
|
print(f"[SALT] Unable to save file to `{filename}`")
|
|
elif output_type in ["MP3", "WAV"] and isinstance(output_data, bytes):
|
|
# Save audio file
|
|
filename = os.path.join(output_path, f"{output_name}.{output_type.lower()}")
|
|
|
|
audio_buffer = io.BytesIO(output_data)
|
|
audio = AudioSegment.from_file(audio_buffer)
|
|
|
|
if output_type == "MP3":
|
|
audio.export(filename, format="mp3")
|
|
else:
|
|
audio.export(filename, format="wav")
|
|
|
|
results.append({
|
|
"filename": os.path.basename(filename),
|
|
"subfolder": subfolder,
|
|
"type": "output"
|
|
})
|
|
|
|
if os.path.exists(filename):
|
|
print(f"[SALT] Saved file to `{filename}`")
|
|
else:
|
|
print(f"[SALT] Unable to save file to `{filename}`")
|
|
|
|
else:
|
|
# Assume string output
|
|
if output_type == "STRING":
|
|
results.append(str(output_data))
|
|
|
|
# Output Dictionary
|
|
ui = {
|
|
"ui": {
|
|
"salt_metadata": [
|
|
{
|
|
"salt_id": unique_id,
|
|
"salt_reference_uuid": asset_id,
|
|
"salt_description": output_desc,
|
|
"salt_asset": is_asset,
|
|
"salt_file_extension": output_type,
|
|
}
|
|
],
|
|
"salt_output": results
|
|
}
|
|
}
|
|
|
|
# Assign images for previews of supported types
|
|
if output_type in ["PNG", "GIF", "WEBP", "JPEG"] and results:
|
|
ui["ui"].update({"images": results})
|
|
|
|
# Print to log
|
|
print(f"[SaltOutput_{unique_id}] Output:")
|
|
from pprint import pprint
|
|
|
|
pprint(ui, indent=4)
|
|
|
|
return ui
|
|
|
|
```
|