Files
2024-06-29 18:50:22 +08:00

228 lines
8.5 KiB
Markdown

---
tags:
- Audio
---
# Salt Workflow Output
## Documentation
- Class name: `SaltOutput`
- Category: `SALT/IO`
- Output node: `True`
The SaltOutput node is designed to handle various types of output data, including images, audio, and text, formatting and saving them appropriately based on the specified output type. It supports a wide range of file formats and is capable of generating complex UI structures to represent the output data effectively.
## Input types
### Required
- **`output_name`**
- Specifies the name of the output, which is used as the base for generating filenames and identifiers in the saved output files.
- Comfy dtype: `STRING`
- Python dtype: `str`
- **`output_desc`**
- Provides a description for the output, which may be used for metadata or UI display purposes.
- Comfy dtype: `STRING`
- Python dtype: `str`
- **`output_type`**
- Determines the format of the output file (e.g., PNG, JPEG, MP3, WAV, STRING), influencing how the output data is processed and saved.
- Comfy dtype: `COMBO[STRING]`
- Python dtype: `str`
- **`output_data`**
- The actual data to be output, which can vary widely in type (e.g., bytes for audio, torch.Tensor for images, string for text outputs).
- Comfy dtype: `*`
- Python dtype: `Union[bytes, torch.Tensor, str]`
### Optional
- **`video_audio`**
- Optional audio data for video outputs, specifying the audio track to be included in video files.
- Comfy dtype: `AUDIO`
- Python dtype: `bytes`
- **`animation_fps`**
- Specifies the frames per second for animated outputs, allowing control over the animation speed.
- Comfy dtype: `INT`
- Python dtype: `int`
- **`animation_quality`**
- Defines the quality level of the animation (DEFAULT or HIGH), affecting the output file's visual fidelity.
- Comfy dtype: `COMBO[STRING]`
- Python dtype: `str`
## Output types
- **`ui`**
- Generates a complex UI structure representing the output data, including metadata and previews for supported file types.
## 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
```