mirror of
https://github.com/comfyanonymous/ComfyUI.git
synced 2026-05-29 18:37:23 +08:00
Merge pull request #4 from alexisrolland/advanced_save
Iterate on new Save Image node
This commit is contained in:
commit
14e114a936
@ -94,6 +94,7 @@ class ConvertColorSpace(IO.ComfyNode):
|
|||||||
|
|
||||||
return IO.NodeOutput(images=img_tensor)
|
return IO.NodeOutput(images=img_tensor)
|
||||||
|
|
||||||
|
|
||||||
class ConvertColorSpaceExtension(ComfyExtension):
|
class ConvertColorSpaceExtension(ComfyExtension):
|
||||||
@override
|
@override
|
||||||
async def get_node_list(self) -> list[type[IO.ComfyNode]]:
|
async def get_node_list(self) -> list[type[IO.ComfyNode]]:
|
||||||
|
|||||||
@ -3,12 +3,18 @@ from __future__ import annotations
|
|||||||
import nodes
|
import nodes
|
||||||
import folder_paths
|
import folder_paths
|
||||||
|
|
||||||
|
import av
|
||||||
|
import io
|
||||||
import json
|
import json
|
||||||
|
|
||||||
import av
|
import av
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import math
|
import math
|
||||||
|
import numpy as np
|
||||||
|
import struct
|
||||||
import torch
|
import torch
|
||||||
|
|
||||||
import struct
|
import struct
|
||||||
import zlib
|
import zlib
|
||||||
import tempfile
|
import tempfile
|
||||||
@ -18,7 +24,8 @@ import numpy as np
|
|||||||
from fractions import Fraction
|
from fractions import Fraction
|
||||||
|
|
||||||
from server import PromptServer
|
from server import PromptServer
|
||||||
from comfy_api.latest import ComfyExtension, IO, UI
|
from comfy_api.latest import ComfyExtension, Input, IO, UI
|
||||||
|
from comfy.cli_args import args
|
||||||
from typing_extensions import override
|
from typing_extensions import override
|
||||||
from comfy.cli_args import args
|
from comfy.cli_args import args
|
||||||
|
|
||||||
@ -831,12 +838,14 @@ class ImageMergeTileList(IO.ComfyNode):
|
|||||||
return IO.NodeOutput(merged_image)
|
return IO.NodeOutput(merged_image)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def create_png_chunk(chunk_type: bytes, data: bytes) -> bytes:
|
def create_png_chunk(chunk_type: bytes, data: bytes) -> bytes:
|
||||||
"""Creates a valid PNG chunk with Length, Type, Data, and CRC32."""
|
"""Creates a valid PNG chunk with Length, Type, Data, and CRC32."""
|
||||||
chunk = struct.pack('>I', len(data)) + chunk_type + data
|
chunk = struct.pack('>I', len(data)) + chunk_type + data
|
||||||
crc = zlib.crc32(chunk_type + data) & 0xffffffff
|
crc = zlib.crc32(chunk_type + data) & 0xffffffff
|
||||||
return chunk + struct.pack('>I', crc)
|
return chunk + struct.pack('>I', crc)
|
||||||
|
|
||||||
|
|
||||||
def inject_comfy_metadata_png(png_bytes, prompt=None, extra_pnginfo=None):
|
def inject_comfy_metadata_png(png_bytes, prompt=None, extra_pnginfo=None):
|
||||||
# IEND chunk is the last 12 bytes of png files
|
# IEND chunk is the last 12 bytes of png files
|
||||||
content = png_bytes[:-12]
|
content = png_bytes[:-12]
|
||||||
@ -939,32 +948,104 @@ class SaveImageAdvanced(IO.ComfyNode):
|
|||||||
def define_schema(cls):
|
def define_schema(cls):
|
||||||
return IO.Schema(
|
return IO.Schema(
|
||||||
node_id="SaveImageAdvanced",
|
node_id="SaveImageAdvanced",
|
||||||
category="image/advanced_io",
|
search_aliases=["save", "save image", "export image", "output image", "write image", "download"],
|
||||||
output_node=True,
|
display_name="Save Image",
|
||||||
|
description="Saves the input images to your ComfyUI output directory.",
|
||||||
|
category="image",
|
||||||
|
essentials_category="Basics",
|
||||||
inputs=[
|
inputs=[
|
||||||
IO.Image.Input("images"),
|
IO.Image.Input(
|
||||||
IO.String.Input("filename_prefix", default="ComfyUI"),
|
"images",
|
||||||
IO.Combo.Input("file_format", options=["png", "exr", "avif"], default="png"),
|
tooltip="The images to save."
|
||||||
IO.Combo.Input("bit_depth", options=["8-bit", "16-bit", "32-bit"], default="8-bit"),
|
),
|
||||||
IO.Boolean.Input("embed_workflow", default=True),
|
IO.String.Input(
|
||||||
IO.Hidden.Input("prompt", type="PROMPT"),
|
"filename_prefix",
|
||||||
IO.Hidden.Input("extra_pnginfo", type="EXTRA_PNGINFO"),
|
default="ComfyUI",
|
||||||
|
tooltip="The prefix for the file to save. This may include formatting information such as %date:yyyy-MM-dd% or %Empty Latent Image.width% to include values from nodes.",
|
||||||
|
),
|
||||||
|
IO.DynamicCombo.Input(
|
||||||
|
"format",
|
||||||
|
options=[
|
||||||
|
IO.DynamicCombo.Option(
|
||||||
|
"png",
|
||||||
|
[
|
||||||
|
IO.Combo.Input(
|
||||||
|
"bit_depth",
|
||||||
|
options=["8-bit", "16-bit"],
|
||||||
|
default="8-bit",
|
||||||
|
advanced=True,
|
||||||
|
),
|
||||||
|
IO.Combo.Input(
|
||||||
|
"color_space",
|
||||||
|
options=["Raw/Data", "sRGB"],
|
||||||
|
default="sRGB",
|
||||||
|
advanced=True,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
IO.DynamicCombo.Option(
|
||||||
|
"avif",
|
||||||
|
[
|
||||||
|
IO.Combo.Input(
|
||||||
|
"bit_depth",
|
||||||
|
options=["8-bit", "10-bit", "12-bit"],
|
||||||
|
default="8-bit",
|
||||||
|
advanced=True,
|
||||||
|
),
|
||||||
|
IO.Combo.Input(
|
||||||
|
"color_space",
|
||||||
|
options=["sRGB"],
|
||||||
|
default="sRGB",
|
||||||
|
advanced=True,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
IO.DynamicCombo.Option(
|
||||||
|
"exr",
|
||||||
|
[
|
||||||
|
IO.Combo.Input(
|
||||||
|
"bit_depth",
|
||||||
|
options=["16-bit (half-float)", "32-bit"],
|
||||||
|
default="16-bit (half-float)",
|
||||||
|
advanced=True,
|
||||||
|
),
|
||||||
|
IO.Combo.Input(
|
||||||
|
"color_space",
|
||||||
|
options=["Linear", "Raw/Data"],
|
||||||
|
default="Linear",
|
||||||
|
advanced=True,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
tooltip="The file format in which to save the image.",
|
||||||
|
),
|
||||||
|
IO.Boolean.Input("embed_workflow", default=True, advanced=True),
|
||||||
],
|
],
|
||||||
outputs=[]
|
hidden=[IO.Hidden.prompt, IO.Hidden.extra_pnginfo],
|
||||||
|
is_output_node=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def execute(cls, images, filename_prefix="ComfyUI", file_format="png", bit_depth="8-bit",
|
def execute(
|
||||||
embed_workflow=True, prompt=None, extra_pnginfo=None) -> IO.NodeOutput:
|
cls,
|
||||||
|
images: Input.Image,
|
||||||
|
filename_prefix: str,
|
||||||
|
format: dict,
|
||||||
|
embed_workflow: bool,
|
||||||
|
prompt=None,
|
||||||
|
extra_pnginfo=None
|
||||||
|
) -> IO.NodeOutput:
|
||||||
output_dir = folder_paths.get_output_directory()
|
output_dir = folder_paths.get_output_directory()
|
||||||
|
full_output_folder, filename, counter, subfolder, filename_prefix = folder_paths.get_save_image_path(filename_prefix, output_dir, images[0].shape[1], images[0].shape[0])
|
||||||
full_output_folder, filename, counter, subfolder, filename_prefix = \
|
|
||||||
folder_paths.get_save_image_path(filename_prefix, output_dir, images[0].shape[1], images[0].shape[0])
|
|
||||||
|
|
||||||
results = list()
|
results = list()
|
||||||
|
|
||||||
for batch_number, image in enumerate(images):
|
for batch_number, image in enumerate(images):
|
||||||
|
# get widget values from dynamic combo
|
||||||
|
extension = format["format"]
|
||||||
|
bit_depth = format["bit_depth"]
|
||||||
|
color_space = format["color_space"]
|
||||||
|
|
||||||
img_tensor = image.clone()
|
img_tensor = image.clone()
|
||||||
|
|
||||||
height, width, num_channels = img_tensor.shape
|
height, width, num_channels = img_tensor.shape
|
||||||
@ -972,6 +1053,7 @@ class SaveImageAdvanced(IO.ComfyNode):
|
|||||||
|
|
||||||
# file pathing
|
# file pathing
|
||||||
filename_with_batch_num = filename.replace("%batch_num%", str(batch_number))
|
filename_with_batch_num = filename.replace("%batch_num%", str(batch_number))
|
||||||
|
|
||||||
file = f"{filename_with_batch_num}_{counter:05}.{file_format}"
|
file = f"{filename_with_batch_num}_{counter:05}.{file_format}"
|
||||||
file_path = os.path.join(full_output_folder, file)
|
file_path = os.path.join(full_output_folder, file)
|
||||||
|
|
||||||
@ -1114,6 +1196,7 @@ class ImagesExtension(ComfyExtension):
|
|||||||
ImageAddNoise,
|
ImageAddNoise,
|
||||||
SaveAnimatedWEBP,
|
SaveAnimatedWEBP,
|
||||||
SaveAnimatedPNG,
|
SaveAnimatedPNG,
|
||||||
|
SaveImageAdvanced,
|
||||||
SaveSVGNode,
|
SaveSVGNode,
|
||||||
ImageStitch,
|
ImageStitch,
|
||||||
ResizeAndPadImage,
|
ResizeAndPadImage,
|
||||||
|
|||||||
3
nodes.py
3
nodes.py
@ -1652,6 +1652,7 @@ class SaveImage:
|
|||||||
ESSENTIALS_CATEGORY = "Basics"
|
ESSENTIALS_CATEGORY = "Basics"
|
||||||
DESCRIPTION = "Saves the input images to your ComfyUI output directory."
|
DESCRIPTION = "Saves the input images to your ComfyUI output directory."
|
||||||
SEARCH_ALIASES = ["save", "save image", "export image", "output image", "write image", "download"]
|
SEARCH_ALIASES = ["save", "save image", "export image", "output image", "write image", "download"]
|
||||||
|
DEPRECATED = True
|
||||||
|
|
||||||
def save_images(self, images, filename_prefix="ComfyUI", prompt=None, extra_pnginfo=None):
|
def save_images(self, images, filename_prefix="ComfyUI", prompt=None, extra_pnginfo=None):
|
||||||
filename_prefix += self.prefix_append
|
filename_prefix += self.prefix_append
|
||||||
@ -2157,7 +2158,7 @@ NODE_DISPLAY_NAME_MAPPINGS = {
|
|||||||
"LatentFromBatch" : "Latent From Batch",
|
"LatentFromBatch" : "Latent From Batch",
|
||||||
"RepeatLatentBatch": "Repeat Latent Batch",
|
"RepeatLatentBatch": "Repeat Latent Batch",
|
||||||
# Image
|
# Image
|
||||||
"SaveImage": "Save Image",
|
"SaveImage": "Save Image (DEPRECATED)",
|
||||||
"PreviewImage": "Preview Image",
|
"PreviewImage": "Preview Image",
|
||||||
"LoadImage": "Load Image",
|
"LoadImage": "Load Image",
|
||||||
"LoadImageMask": "Load Image (as Mask)",
|
"LoadImageMask": "Load Image (as Mask)",
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user