mcpField
C
comfyui-node-outputs

comfyui-node-outputs

github.com/jtydhr88開発308121 回インストール2 週間前に更新
リポジトリ

comfyui-node-outputs について

comfyui-node-outputsは、NodeOutputでデータや画面表示を返す方法を解説するComfyUIノード開発者向けガイドです。メディアのプレビュー、ファイル保存、一時プレビューと永続出力の違いを扱います。

主な機能

  1. 01複数のノード出力形式の設定
  2. 02UIヘルパーによる画像・音声・動画のプレビュー
  3. 03画像と音声ファイルの保存
  4. 04テキストと3Dモデルのプレビュー表示
  5. 05一時プレビューと永続保存の区別

活用シーン

  1. 01プレビューとともにノードデータを返す
  2. 02画像または音声の保存ノードの作成
  3. 03動画、テキスト、3Dモデルの結果表示
  4. 04一時フォルダーまたは出力フォルダーへの保存

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/jtydhr88/comfyui-custom-node-skills --skill comfyui-node-outputs

インストール後、Claude Code で/comfyui-node-outputsを入力して読み込まれたか確認します。

詳細

ComfyUI ノードの出力

ノードは io.NodeOutput を通じてデータを返します。V3 には、プレビューとファイル保存のための UI ヘルパーが組み込まれています。

基本の出力

class SimpleNode(io.ComfyNode):
    @classmethod
    def define_schema(cls):
        return io.Schema(
            node_id="SimpleNode",
            display_name="Simple Node",
            category="example",
            inputs=[io.Float.Input("a"), io.Float.Input("b")],
            outputs=[
                io.Float.Output("SUM"),
                io.Float.Output("PRODUCT"),
            ],
        )

    @classmethod
    def execute(cls, a, b):
        # Values must match output order
        return io.NodeOutput(a + b, a * b)

出力の設定

io.Schema(
    outputs=[
        io.Image.Output("IMAGE"),                    # basic output
        io.Int.Output("COUNT"),                      # integer output
        io.Float.Output("VALUE", display_name="Result"),  # custom display name
        io.String.Output("TEXT", tooltip="The processed text"),
        io.Image.Output("FRAMES", is_output_list=True),  # outputs a list
    ],
)

NodeOutput のバリエーション

# Data only
return io.NodeOutput(image_tensor, mask_tensor)

# UI only (output node with no data outputs)
return io.NodeOutput(ui=ui.PreviewImage(images, cls=cls))

# Data + UI
return io.NodeOutput(image_tensor, ui=ui.PreviewImage(images, cls=cls))

# No output
return io.NodeOutput()

# Block execution
return io.NodeOutput(block_execution="Reason for blocking")

# Node expansion (positional args are outputs, not result= keyword)
return io.NodeOutput(output_ref, expand=graph.finalize())

UI プレビューのヘルパー

comfy_api.latest から ui をインポートします。

from comfy_api.latest import io, ui
PreviewImage

ノードに画像プレビューを表示します。自動的に一時ディレクトリに保存します。

# Constructor: PreviewImage(image, animated=False, cls=None)
class PreviewNode(io.ComfyNode):
    @classmethod
    def define_schema(cls):
        return io.Schema(
            node_id="PreviewNode",
            display_name="Preview Image",
            category="image",
            is_output_node=True,
            inputs=[io.Image.Input("images")],
            outputs=[],
            hidden=[io.Hidden.prompt, io.Hidden.extra_pnginfo],
        )

    @classmethod
    def execute(cls, images):
        return io.NodeOutput(ui=ui.PreviewImage(images, cls=cls))
PreviewMask
# Constructor: PreviewMask(mask, animated=False, cls=None)
# Auto-converts mask to 3-channel grayscale for display
return io.NodeOutput(ui=ui.PreviewMask(masks, cls=cls))
PreviewAudio
# Constructor: PreviewAudio(audio, cls=None)
# Saves as FLAC to temp directory
return io.NodeOutput(ui=ui.PreviewAudio(audio, cls=cls))
PreviewVideo
# Constructor: PreviewVideo(values: list[SavedResult | dict])
return io.NodeOutput(ui=ui.PreviewVideo(saved_video_results))
PreviewText

テキスト出力を表示します。

return io.NodeOutput(ui=ui.PreviewText(value))
PreviewUI3D

3D モデルのプレビューを表示します。

return io.NodeOutput(ui=ui.PreviewUI3D(
    model_file=saved_result,   # SavedResult for the 3D file
    camera_info=camera_dict,   # camera position/target/zoom
    bg_image=image_tensor,     # optional background image (via **kwargs)
))
Saved3DModels(標準の 3D 出力、v0.40+)

すべてのコア 3D 保存/プレビューノードは、モデルを標準の "3d" 出力項目として報告するようになりました(画像と同様に、キュー/アセットに表示されます)。

# Constructor: Saved3DModels(results: list[SavedResult], camera_info=None, model_3d_info=None)
# -> {"3d": results, "camera_info": [camera_info], "model_3d_info": [...]}
return io.NodeOutput(ui=ui.Saved3DModels(
    [ui.SavedResult("model.glb", "", io.FolderType.output)],   # or FolderType.temp for previews
    camera_info=camera_dict,       # extended CameraInfo (quaternion/fov/near/far/frustum)
    model_3d_info=transforms,      # list[Model3DTransform]: position/quaternion/scale per model
))

ui.PreviewUI3DAdvanced(model_file, camera_info, model_3d_info, folder_type=None) はサブクラスとして引き続き存在しますが、model_file は相対パスの 文字列("subfolder/model.glb"、デフォルトのフォルダーは output)になり、{"result": [...]} ではなく、上記の "3d" 形式で出力します。

画像の保存

ImageSaveHelper を使用する

ui.ImageSaveHelper クラスは、さまざまな画像形式に対応する静的メソッドを提供します。

# Save as PNG (returns list[SavedResult])
results = ui.ImageSaveHelper.save_images(
    images,                              # tensor [B,H,W,C]
    filename_prefix="ComfyUI",
    folder_type=io.FolderType.output,    # output, temp, or input
    cls=cls,                             # node class (for metadata)
    compress_level=4,
)

# Save and get UI object directly (saves to output folder)
saved_ui = ui.ImageSaveHelper.get_save_images_ui(images, "ComfyUI", cls=cls)
return io.NodeOutput(ui=saved_ui)

# Save animated PNG
result = ui.ImageSaveHelper.save_animated_png(
    images, "anim", io.FolderType.output, cls=cls, fps=12.0, compress_level=4
)

# Save animated PNG and get UI
saved_ui = ui.ImageSaveHelper.get_save_animated_png_ui(images, "anim", cls=cls, fps=12.0, compress_level=4)

# Save animated WebP
result = ui.ImageSaveHelper.save_animated_webp(
    images, "anim", io.FolderType.output, cls=cls,
    fps=12.0, lossless=False, quality=80, method=4
)

# Save animated WebP and get UI
saved_ui = ui.ImageSaveHelper.get_save_animated_webp_ui(
    images, "anim", cls=cls, fps=12.0, lossless=False, quality=80, method=4
)

シンプルな保存ノードの例:

class SaveImageNode(io.ComfyNode):
    @classmethod
    def define_schema(cls):
        return io.Schema(
            node_id="SaveImageNode",
            display_name="Save Image",
            category="image",
            is_output_node=True,
            inputs=[
                io.Image.Input("images"),
                io.String.Input("filename_prefix", default="ComfyUI"),
            ],
            outputs=[],
            hidden=[io.Hidden.prompt, io.Hidden.extra_pnginfo],
        )

    @classmethod
    def execute(cls, images, filename_prefix):
        saved = ui.ImageSaveHelper.get_save_images_ui(images, filename_prefix, cls=cls)
        return io.NodeOutput(ui=saved)
画像を手動で保存する
import os
import json
import numpy as np
from PIL import Image as PILImage
from PIL.PngImagePlugin import PngInfo
import folder_paths

class CustomSaveNode(io.ComfyNode):
    @classmethod
    def define_schema(cls):
        return io.Schema(
            node_id="CustomSaveNode",
            display_name="Custom Save",
            category="image",
            is_output_node=True,
            inputs=[
                io.Image.Input("images"),
                io.String.Input("prefix", default="output"),
            ],
            outputs=[],
            hidden=[io.Hidden.prompt, io.Hidden.extra_pnginfo],
        )

    @classmethod
    def execute(cls, images, prefix):
        output_dir = folder_paths.get_output_directory()
        results = []

        for i, image in enumerate(images):
            # Convert tensor to PIL
            img_array = np.clip(255.0 * image.cpu().numpy(), 0, 255).astype(np.uint8)
            pil_image = PILImage.fromarray(img_array)

            # Add metadata
            metadata = PngInfo()
            if cls.hidden.prompt:
                metadata.add_text("prompt", json.dumps(cls.hidden.prompt))
            if cls.hidden.extra_pnginfo:
                for k, v in cls.hidden.extra_pnginfo.items():
                    metadata.add_text(k, json.dumps(v))

            # Save with counter
            filename = f"{prefix}_{i:05d}.png"
            filepath = os.path.join(output_dir, filename)
            pil_image.save(filepath, pnginfo=metadata)

            results.append(ui.SavedResult(
                filename=filename,
                subfolder="",
                type=io.FolderType.output,
            ))

        return io.NodeOutput(ui=ui.SavedImages(results))

オーディオの保存

ui.AudioSaveHelper は FLAC、MP3、Opus 形式に対応しています。

# Save audio (returns list[SavedResult])
results = ui.AudioSaveHelper.save_audio(
    audio,                               # {"waveform": Tensor, "sample_rate": int}
    filename_prefix="audio",
    folder_type=io.FolderType.output,
    cls=cls,
    format="flac",                       # "flac", "mp3", or "opus" (anything else raises ValueError)
    quality="128k",                      # MP3: "V0","128k","320k"; Opus: "64k"-"320k"
)

# Save and get UI object
saved_ui = ui.AudioSaveHelper.get_save_audio_ui(audio, "audio", cls=cls, format="flac", quality="128k")
return io.NodeOutput(ui=saved_ui)
class SaveAudioNode(io.ComfyNode):
    @classmethod
    def define_schema(cls):
        return io.Schema(
            node_id="SaveAudioNode",
            display_name="Save Audio",
            category="audio",
            is_output_node=True,
            inputs=[
                io.Audio.Input("audio"),
                io.String.Input("prefix", default="audio"),
                io.Combo.Input("format", options=["flac", "mp3", "opus"], default="flac"),
            ],
            outputs=[],
            hidden=[io.Hidden.prompt, io.Hidden.extra_pnginfo],
        )

    @classmethod
    def execute(cls, audio, prefix, format):
        saved = ui.AudioSaveHelper.get_save_audio_ui(audio, prefix, cls=cls, format=format)
        return io.NodeOutput(ui=saved)

一時プレビューと永続的な保存

  • プレビュー(PreviewImage など)は temp ディレクトリに保存され、一時的なものです
  • 保存(ImageSaveHelper.save_images)は output ディレクトリに永続的に保存されます
  • io.FolderType.temp、io.FolderType.output、または io.FolderType.input を使用します

コアによる UI 出力の後処理

execute の後、UI 辞書内のすべてのファイル項目({"filename", "subfolder", "type"})は、送信/保存される前に拡張されます。

  • metadata:画像/動画ファイルには、項目に metadata がすでに存在する場合を除き、{"kind", "width", "height"}(動画の場合はさらに duration、fps、frame_count)が追加されます(v0.40+)。
  • アセットシステムが有効な場合(デフォルトでは Windows スタンドアロンビルドのみ)、id/preview_id が追加されます。キャッシュヒットの再生時には、すべての辞書項目から id/preview_id が取り除かれ、asset_preview キーはコアによって消費されます。
  • UI 辞書は実行のたびにディープコピーされます。

そのため、UI 項目では id、preview_id、metadata、asset_preview を独自のキーとして使用せず、UI の値は小さく、JSON シリアライズ可能なものにしてください。

V1 の出力パターン(レガシーの参考情報)

class V1SaveNode:
    RETURN_TYPES = ()
    OUTPUT_NODE = True
    FUNCTION = "save"

    def save(self, images, prefix):
        # ... save logic ...
        return {
            "ui": {
                "images": [
                    {"filename": "out.png", "subfolder": "", "type": "output"}
                ]
            }
        }

# Data + UI in V1:
class V1PreviewAndOutput:
    RETURN_TYPES = ("IMAGE",)
    OUTPUT_NODE = True
    FUNCTION = "run"

    def run(self, image):
        # ... preview logic ...
        return {
            "ui": {"images": [...]},
            "result": (processed_image,),
        }

関連項目

  • comfyui-node-basics - ノードの構造とスキーマ
  • comfyui-node-datatypes - データ型の形式
  • comfyui-node-lifecycle - 実行フロー

コメント