converted all s3 nodes to v3

This commit is contained in:
Inoland
2026-04-04 13:48:39 +04:00
parent d752278315
commit fb8ec0ee32
14 changed files with 564 additions and 627 deletions
@@ -2,39 +2,45 @@ from pathlib import Path
from inopyutils import ino_is_err, InoUtilHelper
import folder_paths
from comfy_api.latest import io
from .s3_helper import S3Helper, S3_EMPTY_CONFIG_STRING
from ..node_helper import PARENT_FOLDER_OPTIONS, resolve_comfy_path
class InoS3DownloadAudio:
class InoS3DownloadAudio(io.ComfyNode):
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"enabled": ("BOOLEAN", {"default": True, "label_off": "OFF", "label_on": "ON"}),
"s3_key": ("STRING", {"default": "input/example.wav"}),
"parent_folder": (PARENT_FOLDER_OPTIONS, {"default": "temp"}),
"folder": ("STRING", {"default": ""}),
},
"optional": {
"s3_config": ("STRING", {"default": S3_EMPTY_CONFIG_STRING, "tooltip": "you can leave it empty and pass it with env vars"}),
"bucket_name": ("STRING", {"default": "default"}),
}
}
def define_schema(cls):
return io.Schema(
node_id="InoS3DownloadAudio",
display_name="Ino S3 Download Audio",
category="InoS3Helper",
description="Downloads an audio file from S3 and returns it as an AUDIO tensor.",
inputs=[
io.Boolean.Input("enabled", default=True, label_off="OFF", label_on="ON"),
io.String.Input("s3_key", default="input/example.wav"),
io.Combo.Input("parent_folder", options=PARENT_FOLDER_OPTIONS, default="temp"),
io.String.Input("folder", default=""),
io.String.Input("s3_config", default=S3_EMPTY_CONFIG_STRING, optional=True, tooltip="you can leave it empty and pass it with env vars"),
io.String.Input("bucket_name", default="default", optional=True),
],
outputs=[
io.Boolean.Output(display_name="success"),
io.String.Output(display_name="message"),
io.String.Output(display_name="rel_path"),
io.String.Output(display_name="abs_path"),
io.Audio.Output(display_name="audio"),
],
)
CATEGORY = "InoS3Helper"
RETURN_TYPES = ("BOOLEAN", "STRING", "STRING", "STRING", "AUDIO", )
RETURN_NAMES = ("success", "message", "rel_path", "abs_path", "audio", )
FUNCTION = "function"
async def function(self, enabled, s3_key, parent_folder, folder, s3_config=None, bucket_name=None):
@classmethod
async def execute(cls, enabled, s3_key, parent_folder, folder, s3_config=None, bucket_name=None) -> io.NodeOutput:
if not enabled:
return (False, "not enabled", "", "", None, )
return io.NodeOutput(False, "not enabled", "", "", None)
validate_s3_key = S3Helper.validate_s3_key(s3_key)
if not validate_s3_key["success"]:
return (False, validate_s3_key["msg"], "", "", None, )
return io.NodeOutput(False, validate_s3_key["msg"], "", "", None)
random_str = InoUtilHelper.get_date_time_utc_base64()
file_name = f'{random_str}{Path(s3_key).suffix}'
@@ -44,24 +50,20 @@ class InoS3DownloadAudio:
s3_instance = S3Helper.get_instance(s3_config)
if ino_is_err(s3_instance):
return (False, s3_instance["msg"], "", "", None, )
return io.NodeOutput(False, s3_instance["msg"], "", "", None)
s3_instance = s3_instance["instance"]
downloaded = await s3_instance.download_file(
s3_key=s3_key,
local_file_path=abs_path
)
downloaded = await s3_instance.download_file(s3_key=s3_key, local_file_path=abs_path)
if not downloaded["success"]:
return (downloaded["success"], downloaded["msg"], rel_path, abs_path, None, )
return io.NodeOutput(downloaded["success"], downloaded["msg"], rel_path, abs_path, None)
from comfy_extras.nodes_audio import LoadAudio
# Build annotated name for ComfyUI's audio loader
folder_suffix = folder if folder else ""
annotated_name = f"{folder_suffix}/{file_name} [{parent_folder}]" if folder_suffix else f"{file_name} [{parent_folder}]"
load_audio = LoadAudio.execute(audio=annotated_name)
if load_audio[0]:
return (True, "Success", rel_path, abs_path, load_audio[0],)
return io.NodeOutput(True, "Success", rel_path, abs_path, load_audio[0])
return (False, "failed to load the audio", rel_path, abs_path, None,)
return io.NodeOutput(False, "failed to load the audio", rel_path, abs_path, None)
@@ -1,53 +1,56 @@
from pathlib import Path
import folder_paths
from inopyutils import ino_ok, ino_err, ino_is_err
from inopyutils import ino_is_err
from comfy_api.latest import io
from .s3_helper import S3Helper, S3_EMPTY_CONFIG_STRING
from ..node_helper import PARENT_FOLDER_OPTIONS, resolve_comfy_path
class InoS3DownloadFile:
class InoS3DownloadFile(io.ComfyNode):
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"enabled": ("BOOLEAN", {"default": True, "label_off": "OFF", "label_on": "ON"}),
"s3_key": ("STRING", {"default": "input/example.png"}),
"parent_folder": (PARENT_FOLDER_OPTIONS,),
"folder": ("STRING", {"default": "s3download/"}),
},
"optional": {
"s3_config": ("STRING", {"default": S3_EMPTY_CONFIG_STRING, "tooltip": "you can leave it empty and pass it with env vars"}),
"bucket_name": ("STRING", {"default": "default"}),
}
}
def define_schema(cls):
return io.Schema(
node_id="InoS3DownloadFile",
display_name="Ino S3 Download File",
category="InoS3Helper",
description="Downloads a file from S3 to a local folder.",
inputs=[
io.Boolean.Input("enabled", default=True, label_off="OFF", label_on="ON"),
io.String.Input("s3_key", default="input/example.png"),
io.Combo.Input("parent_folder", options=PARENT_FOLDER_OPTIONS),
io.String.Input("folder", default="s3download/"),
io.String.Input("s3_config", default=S3_EMPTY_CONFIG_STRING, optional=True, tooltip="you can leave it empty and pass it with env vars"),
io.String.Input("bucket_name", default="default", optional=True),
],
outputs=[
io.Boolean.Output(display_name="success"),
io.String.Output(display_name="message"),
io.String.Output(display_name="rel_path"),
io.String.Output(display_name="abs_path"),
],
)
CATEGORY = "InoS3Helper"
RETURN_TYPES = ("BOOLEAN", "STRING", "STRING", "STRING", )
RETURN_NAMES = ("success", "message", "rel_path", "abs_path", )
FUNCTION = "function"
async def function(self, enabled, s3_key, parent_folder, folder, s3_config=None, bucket_name=None):
@classmethod
async def execute(cls, enabled, s3_key, parent_folder, folder, s3_config=None, bucket_name=None) -> io.NodeOutput:
if not enabled:
return (False, "not enabled", "", "",)
return io.NodeOutput(False, "not enabled", "", "")
validate_s3_key = S3Helper.validate_s3_key(s3_key)
if not validate_s3_key["success"]:
return (False, validate_s3_key["msg"], "", "",)
return io.NodeOutput(False, validate_s3_key["msg"], "", "")
save_path = S3Helper.get_save_path(s3_key, folder)
rel_path, abs_path = resolve_comfy_path(parent_folder, save_path)
rel_path, abs_path = resolve_comfy_path(parent_folder, str(save_path))
if not Path(abs_path).is_dir():
Path(abs_path).parent.mkdir(parents=True, exist_ok=True)
s3_instance = S3Helper.get_instance(s3_config)
if ino_is_err(s3_instance):
return (False, s3_instance["msg"], "", "",)
return io.NodeOutput(False, s3_instance["msg"], "", "")
s3_instance = s3_instance["instance"]
s3_result = await s3_instance.download_file(
s3_key=s3_key,
local_file_path=abs_path
)
return (s3_result["success"], s3_result["msg"], rel_path, abs_path, )
s3_result = await s3_instance.download_file(s3_key=s3_key, local_file_path=abs_path)
return io.NodeOutput(s3_result["success"], s3_result["msg"], rel_path, abs_path)
@@ -1,59 +1,59 @@
from pathlib import Path
import folder_paths
from inopyutils import ino_is_err
from inopyutils import ino_ok, ino_err, ino_is_err
from comfy_api.latest import io
from .s3_helper import S3Helper, S3_EMPTY_CONFIG_STRING
from ..node_helper import PARENT_FOLDER_OPTIONS, resolve_comfy_path
class InoS3DownloadFolder:
class InoS3DownloadFolder(io.ComfyNode):
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"enabled": ("BOOLEAN", {"default": True, "label_off": "OFF", "label_on": "ON"}),
"s3_key": ("STRING", {"default": "input/example.png"}),
"parent_folder": (PARENT_FOLDER_OPTIONS,),
"folder": ("STRING", {"default": "input/"}),
},
"optional": {
"s3_config": ("STRING", {"default": S3_EMPTY_CONFIG_STRING, "tooltip": "you can leave it empty and pass it with env vars"}),
"bucket_name": ("STRING", {"default": "default"}),
"max_concurrent": ("INT", {"default": 5, "min": 1, "max": 10}),
}
}
def define_schema(cls):
return io.Schema(
node_id="InoS3DownloadFolder",
display_name="Ino S3 Download Folder",
category="InoS3Helper",
description="Downloads an entire folder from S3 with concurrent downloads.",
inputs=[
io.Boolean.Input("enabled", default=True, label_off="OFF", label_on="ON"),
io.String.Input("s3_key", default="input/example.png"),
io.Combo.Input("parent_folder", options=PARENT_FOLDER_OPTIONS),
io.String.Input("folder", default="input/"),
io.String.Input("s3_config", default=S3_EMPTY_CONFIG_STRING, optional=True, tooltip="you can leave it empty and pass it with env vars"),
io.String.Input("bucket_name", default="default", optional=True),
io.Int.Input("max_concurrent", default=5, min=1, max=10, optional=True),
],
outputs=[
io.Boolean.Output(display_name="success"),
io.String.Output(display_name="message"),
io.String.Output(display_name="rel_path"),
io.String.Output(display_name="abs_path"),
],
)
CATEGORY = "InoS3Helper"
RETURN_TYPES = ("BOOLEAN", "STRING", "STRING", "STRING", )
RETURN_NAMES = ("success", "message", "rel_path", "abs_path", )
FUNCTION = "function"
async def function(self, enabled, s3_key, parent_folder, folder, s3_config=None, bucket_name=None, max_concurrent=5):
@classmethod
async def execute(cls, enabled, s3_key, parent_folder, folder, s3_config=None, bucket_name=None, max_concurrent=5) -> io.NodeOutput:
if not enabled:
return (False, "not enabled", "", "", )
return io.NodeOutput(False, "not enabled", "", "")
validate_s3_key = S3Helper.validate_s3_key(s3_key)
if not validate_s3_key["success"]:
return (False, validate_s3_key["msg"], "", "",)
return io.NodeOutput(False, validate_s3_key["msg"], "", "")
rel_path, abs_path = resolve_comfy_path(parent_folder, folder)
if Path(abs_path).is_file():
return (False, "Save path is a file", "", "",)
return io.NodeOutput(False, "Save path is a file", "", "")
if not Path(abs_path).is_dir():
Path(abs_path).mkdir(parents=True, exist_ok=True)
s3_instance = S3Helper.get_instance(s3_config)
if ino_is_err(s3_instance):
return (False, s3_instance["msg"], "", "",)
return io.NodeOutput(False, s3_instance["msg"], "", "")
s3_instance = s3_instance["instance"]
s3_result = await s3_instance.download_folder(
s3_folder_key=s3_key,
local_folder_path=abs_path,
max_concurrent=max_concurrent
)
return (s3_result["success"], s3_result["msg"], rel_path, abs_path, )
s3_result = await s3_instance.download_folder(s3_folder_key=s3_key, local_folder_path=abs_path, max_concurrent=max_concurrent)
return io.NodeOutput(s3_result["success"], s3_result["msg"], rel_path, abs_path)
@@ -2,41 +2,48 @@ from pathlib import Path
from inopyutils import ino_is_err, InoUtilHelper
import folder_paths
from comfy_api.latest import io
from .s3_helper import S3Helper, S3_EMPTY_CONFIG_STRING
from ..node_helper import PARENT_FOLDER_OPTIONS, resolve_comfy_path, load_image
class InoS3DownloadImage:
class InoS3DownloadImage(io.ComfyNode):
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"enabled": ("BOOLEAN", {"default": True, "label_off": "OFF", "label_on": "ON"}),
"s3_key": ("STRING", {"default": "input/example.png"}),
"parent_folder": (PARENT_FOLDER_OPTIONS, {"default": "temp"}),
"folder": ("STRING", {"default": ""}),
},
"optional": {
"s3_config": ("STRING", {"default": S3_EMPTY_CONFIG_STRING, "tooltip": "you can leave it empty and pass it with env vars"}),
"bucket_name": ("STRING", {"default": "default"}),
}
}
def define_schema(cls):
return io.Schema(
node_id="InoS3DownloadImage",
display_name="Ino S3 Download Image",
category="InoS3Helper",
description="Downloads an image from S3 and returns it as an IMAGE tensor with mask.",
inputs=[
io.Boolean.Input("enabled", default=True, label_off="OFF", label_on="ON"),
io.String.Input("s3_key", default="input/example.png"),
io.Combo.Input("parent_folder", options=PARENT_FOLDER_OPTIONS, default="temp"),
io.String.Input("folder", default=""),
io.String.Input("s3_config", default=S3_EMPTY_CONFIG_STRING, optional=True, tooltip="you can leave it empty and pass it with env vars"),
io.String.Input("bucket_name", default="default", optional=True),
],
outputs=[
io.Boolean.Output(display_name="success"),
io.String.Output(display_name="message"),
io.String.Output(display_name="rel_path"),
io.String.Output(display_name="abs_path"),
io.Image.Output(display_name="image"),
io.Mask.Output(display_name="mask"),
],
)
CATEGORY = "InoS3Helper"
RETURN_TYPES = ("BOOLEAN", "STRING", "STRING", "STRING", "IMAGE", "MASK", )
RETURN_NAMES = ("success", "message", "rel_path", "abs_path", "image", "mask", )
FUNCTION = "function"
async def function(self, enabled, s3_key, parent_folder, folder, s3_config=None, bucket_name=None):
@classmethod
async def execute(cls, enabled, s3_key, parent_folder, folder, s3_config=None, bucket_name=None) -> io.NodeOutput:
from nodes import EmptyImage
empty_image = EmptyImage().generate(512, 512)[0]
if not enabled:
return (False, "not enabled", "", "", empty_image, None, )
return io.NodeOutput(False, "not enabled", "", "", empty_image, None)
validate_s3_key = S3Helper.validate_s3_key(s3_key)
if not validate_s3_key["success"]:
return (False, validate_s3_key["msg"], "", "", empty_image, None,)
return io.NodeOutput(False, validate_s3_key["msg"], "", "", empty_image, None)
random_str = InoUtilHelper.get_date_time_utc_base64()
file_name = f'{random_str}{Path(s3_key).suffix}'
@@ -46,16 +53,12 @@ class InoS3DownloadImage:
s3_instance = S3Helper.get_instance(s3_config)
if ino_is_err(s3_instance):
return (False, s3_instance["msg"], "", "", empty_image, None,)
return io.NodeOutput(False, s3_instance["msg"], "", "", empty_image, None)
s3_instance = s3_instance["instance"]
downloaded = await s3_instance.download_file(
s3_key=s3_key,
local_file_path=abs_path
)
downloaded = await s3_instance.download_file(s3_key=s3_key, local_file_path=abs_path)
if not downloaded["success"]:
return (downloaded["success"], downloaded["msg"], rel_path, abs_path, empty_image, None, )
return io.NodeOutput(downloaded["success"], downloaded["msg"], rel_path, abs_path, empty_image, None)
output_image, output_mask = load_image(downloaded["local_file"])
return (downloaded["success"], downloaded["msg"], rel_path, abs_path, output_image, output_mask, )
return io.NodeOutput(downloaded["success"], downloaded["msg"], rel_path, abs_path, output_image, output_mask)
@@ -1,51 +1,48 @@
from pathlib import Path
import folder_paths
from inopyutils import ino_is_err
from inopyutils import ino_ok, ino_err, ino_is_err
from comfy_api.latest import io
from .s3_helper import S3Helper, S3_EMPTY_CONFIG_STRING
class InoS3GetDownloadURL:
class InoS3GetDownloadURL(io.ComfyNode):
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"enabled": ("BOOLEAN", {"default": True, "label_off": "OFF", "label_on": "ON"}),
"s3_key": ("STRING", {"default": "input/example.png"}),
"expires_in": ("INT", {"default": 3600, "min": 1, "max": 3600, "step": 1}),
"as_attachment": ("BOOLEAN", {"default": False}),
"filename": ("STRING", {"default": ""}),
},
"optional": {
"s3_config": ("STRING", {"default": S3_EMPTY_CONFIG_STRING, "tooltip": "you can leave it empty and pass it with env vars"}),
"bucket_name": ("STRING", {"default": "default"}),
}
}
def define_schema(cls):
return io.Schema(
node_id="InoS3GetDownloadURL",
display_name="Ino S3 Get Download URL",
category="InoS3Helper",
description="Generates a presigned download URL for an S3 object.",
inputs=[
io.Boolean.Input("enabled", default=True, label_off="OFF", label_on="ON"),
io.String.Input("s3_key", default="input/example.png"),
io.Int.Input("expires_in", default=3600, min=1, max=3600, step=1),
io.Boolean.Input("as_attachment", default=False),
io.String.Input("filename", default=""),
io.String.Input("s3_config", default=S3_EMPTY_CONFIG_STRING, optional=True, tooltip="you can leave it empty and pass it with env vars"),
io.String.Input("bucket_name", default="default", optional=True),
],
outputs=[
io.Boolean.Output(display_name="success"),
io.String.Output(display_name="message"),
io.String.Output(display_name="download_url"),
io.String.Output(display_name="filename"),
],
)
CATEGORY = "InoS3Helper"
RETURN_TYPES = ("BOOLEAN", "STRING", "STRING", "STRING", "STRING",)
RETURN_NAMES = ("success", "msg", "result", "download_url", "filename",)
FUNCTION = "function"
async def function(self, enabled:bool, s3_key:str, expires_in:int = 3600, as_attachment:bool = False, filename:str = None, s3_config:str = "{}", bucket_name:str = "default"):
@classmethod
async def execute(cls, enabled, s3_key, expires_in=3600, as_attachment=False, filename=None, s3_config=None, bucket_name=None) -> io.NodeOutput:
if not enabled:
return (False, "not enabled", "", "", "",)
return io.NodeOutput(False, "not enabled", "", "")
validate_s3_key = S3Helper.validate_s3_key(s3_key)
if not validate_s3_key["success"]:
return (False, validate_s3_key["msg"], "", "", "",)
return io.NodeOutput(False, validate_s3_key["msg"], "", "")
s3_instance = S3Helper.get_instance(s3_config)
if ino_is_err(s3_instance):
return (False, s3_instance["msg"], "", "", "",)
return io.NodeOutput(False, s3_instance["msg"], "", "")
s3_instance = s3_instance["instance"]
s3_result = await s3_instance.get_download_link(
s3_key=s3_key,
#bucket_name=bucket_name,
expires_in=expires_in,
as_attachment=as_attachment,
filename=filename,
)
return (s3_result["success"], s3_result["msg"], str(s3_result), s3_result["url"], s3_result["filename"],)
s3_result = await s3_instance.get_download_link(s3_key=s3_key, expires_in=expires_in, as_attachment=as_attachment, filename=filename)
return io.NodeOutput(s3_result["success"], s3_result["msg"], s3_result["url"], s3_result["filename"])
+34 -70
View File
@@ -3,6 +3,8 @@ from pathlib import Path
from inopyutils import InoS3Helper, InoJsonHelper, ino_ok, ino_err, ino_is_err
from comfy_api.latest import io
S3_EMPTY_CONFIG = {
"access_key_id": "",
"access_key_secret": "",
@@ -61,14 +63,14 @@ class S3Helper:
if s3_config_dict.get("access_key_id", None) is None:
return {
"success": False,
"msg": f"S3 configuration missing access_key_id",
"msg": "S3 configuration missing access_key_id",
"config": ""
}
if s3_config_dict.get("access_key_secret", None) is None:
return {
"success": False,
"msg": f"S3 configuration missing access_key_secret",
"msg": "S3 configuration missing access_key_secret",
"config": ""
}
@@ -81,61 +83,19 @@ class S3Helper:
@staticmethod
def validate_s3_key(s3_key) -> dict:
if not s3_key or not s3_key.strip():
return {
"success": False,
"msg": "S3 key is required and cannot be empty"
}
return {
"success": True,
"msg": "S3 key is valid"
}
return {"success": False, "msg": "S3 key is required and cannot be empty"}
return {"success": True, "msg": "S3 key is valid"}
@staticmethod
def validate_local_path(local_path) -> dict:
if not local_path:
return {
"success": False,
"msg": "Local path is required and cannot be empty"
}
return {"success": False, "msg": "Local path is required and cannot be empty"}
if not Path(local_path).is_file() and not Path(local_path).is_dir():
return {
"success": False,
"msg": "Local path does not exist"
}
return {
"success": True,
"msg": "Local path is valid"
}
return {"success": False, "msg": "Local path does not exist"}
return {"success": True, "msg": "Local path is valid"}
@staticmethod
def get_save_path(s3_key: str, save_path: str):
"""
Generate a local file path for saving an S3 object, handling various path formats.
This function intelligently determines the appropriate local file path based on the
provided save_path format and the S3 key. It handles three scenarios:
1. Directory path - saves file with original name from S3 key
2. Full file path with extension - replaces extension with S3 object's extension
3. File path without extension - appends S3 object's extension
Args:
s3_key (str): The S3 object key (path) from which to extract the filename and extension.
Example: "folder/subfolder/document.pdf"
save_path (str): The desired local save path. Can be:
- Directory path ending with '/' or '\\': "/local/dir/"
- Full file path with extension: "/local/dir/myfile.txt"
- File path without extension: "/local/dir/myfile"
Returns:
Path: A pathlib.Path object representing the complete local file path where
the S3 object should be saved, including the appropriate file extension.
Note:
- The function preserves the file extension from the S3 key in all cases
- Both forward slashes (/) and backslashes (\\) are recognized as directory indicators
- If save_path has an extension, it will be replaced with the S3 object's extension
"""
save_path_obj = Path(save_path)
s3_key_obj = Path(s3_key)
@@ -151,26 +111,31 @@ class S3Helper:
return local_file_path
class InoS3Config:
class InoS3Config(io.ComfyNode):
@classmethod
def INPUT_TYPES(s):
return {
"required":{
"access_key_id": ("STRING", {"default": ""}),
"access_key_secret": ("STRING", {"default": ""}),
"endpoint_url": ("STRING", {"default": ""}),
"region_name": ("STRING", {"default": ""}),
"bucket_name": ("STRING", {"default": ""}),
}
}
def define_schema(cls):
return io.Schema(
node_id="InoS3Config",
display_name="Ino S3 Config",
category="InoS3Helper",
description="Creates an S3 configuration string from credentials.",
inputs=[
io.String.Input("access_key_id", default=""),
io.String.Input("access_key_secret", default=""),
io.String.Input("endpoint_url", default=""),
io.String.Input("region_name", default=""),
io.String.Input("bucket_name", default=""),
],
outputs=[
io.Boolean.Output(display_name="success"),
io.String.Output(display_name="config"),
],
)
CATEGORY = "InoS3Helper"
RETURN_TYPES = ("BOOLEAN", "STRING", )
RETURN_NAMES = ("success", "config", )
FUNCTION = "function"
async def function(self, access_key_id, access_key_secret, endpoint_url, region_name, bucket_name):
s3_config ={
@classmethod
async def execute(cls, access_key_id, access_key_secret, endpoint_url, region_name, bucket_name) -> io.NodeOutput:
s3_config = {
"access_key_id": access_key_id,
"access_key_secret": access_key_secret,
"endpoint_url": endpoint_url,
@@ -179,6 +144,5 @@ class InoS3Config:
}
dict_to_string = InoJsonHelper.dict_to_string(s3_config)
if not dict_to_string["success"]:
return (False, dict_to_string["msg"], )
return (True, dict_to_string["data"], )
return io.NodeOutput(False, dict_to_string["msg"])
return io.NodeOutput(True, dict_to_string["data"])
@@ -2,45 +2,53 @@ from pathlib import Path
from inopyutils import ino_is_err
import folder_paths
from comfy_api.latest import io
from .s3_helper import S3Helper, S3_EMPTY_CONFIG_STRING
from ..node_helper import any_type, PARENT_FOLDER_OPTIONS, resolve_comfy_path
from ..node_helper import PARENT_FOLDER_OPTIONS, resolve_comfy_path
class InoS3SyncFolder:
class InoS3SyncFolder(io.ComfyNode):
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"execute": (any_type,),
"enabled": ("BOOLEAN", {"default": True, "label_off": "OFF", "label_on": "ON"}),
"s3_key": ("STRING", {"default": ""}),
"parent_folder": (PARENT_FOLDER_OPTIONS,),
"folder": ("STRING", {"default": "sync/"}),
"sync_local": ("BOOLEAN", {"default": True, "label_off": "Upload (local->S3)", "label_on": "Download (S3->local)"}),
},
"optional": {
"s3_config": ("STRING", {"default": S3_EMPTY_CONFIG_STRING, "tooltip": "you can leave it empty and pass it with env vars"}),
"concurrency": ("INT", {"default": 5, "min": 1, "max": 10}),
}
}
def define_schema(cls):
return io.Schema(
node_id="InoS3SyncFolder",
display_name="Ino S3 Sync Folder",
category="InoS3Helper",
description="Syncs a local folder with S3 bidirectionally (upload or download).",
is_output_node=True,
inputs=[
io.AnyType.Input("execute"),
io.Boolean.Input("enabled", default=True, label_off="OFF", label_on="ON"),
io.String.Input("s3_key", default=""),
io.Combo.Input("parent_folder", options=PARENT_FOLDER_OPTIONS),
io.String.Input("folder", default="sync/"),
io.Boolean.Input("sync_local", default=True, label_off="Upload (local->S3)", label_on="Download (S3->local)"),
io.String.Input("s3_config", default=S3_EMPTY_CONFIG_STRING, optional=True, tooltip="you can leave it empty and pass it with env vars"),
io.Int.Input("concurrency", default=5, min=1, max=10, optional=True),
],
outputs=[
io.Boolean.Output(display_name="success"),
io.String.Output(display_name="message"),
io.String.Output(display_name="rel_path"),
io.String.Output(display_name="abs_path"),
io.Int.Output(display_name="downloaded"),
io.Int.Output(display_name="uploaded"),
io.Int.Output(display_name="skipped_unchanged"),
io.Int.Output(display_name="failed"),
],
)
CATEGORY = "InoS3Helper"
RETURN_TYPES = ("BOOLEAN", "STRING", "STRING", "STRING", "INT", "INT", "INT", "INT",)
RETURN_NAMES = ("success", "message", "rel_path", "abs_path", "downloaded", "uploaded", "skipped_unchanged", "failed",)
FUNCTION = "function"
OUTPUT_NODE = True
async def function(self, execute, enabled, s3_key, parent_folder, folder, sync_local, s3_config=None, concurrency=5):
@classmethod
async def execute(cls, execute, enabled, s3_key, parent_folder, folder, sync_local, s3_config=None, concurrency=5) -> io.NodeOutput:
if not enabled:
return (False, "not enabled", "", "", 0, 0, 0, 0,)
return io.NodeOutput(False, "not enabled", "", "", 0, 0, 0, 0)
if not execute:
return (False, "execute empty", "", "", 0, 0, 0, 0,)
return io.NodeOutput(False, "execute empty", "", "", 0, 0, 0, 0)
validate_s3_key = S3Helper.validate_s3_key(s3_key)
if not validate_s3_key["success"]:
return (False, validate_s3_key["msg"], "", "", 0, 0, 0, 0,)
return io.NodeOutput(False, validate_s3_key["msg"], "", "", 0, 0, 0, 0)
rel_path, abs_path = resolve_comfy_path(parent_folder, folder)
@@ -50,23 +58,13 @@ class InoS3SyncFolder:
s3_instance = S3Helper.get_instance(s3_config)
if ino_is_err(s3_instance):
return (False, s3_instance["msg"], "", "", 0, 0, 0, 0,)
return io.NodeOutput(False, s3_instance["msg"], "", "", 0, 0, 0, 0)
s3_instance = s3_instance["instance"]
s3_result = await s3_instance.sync_folder(
s3_key=s3_key,
local_folder_path=abs_path,
sync_local=sync_local,
concurrency=concurrency,
)
s3_result = await s3_instance.sync_folder(s3_key=s3_key, local_folder_path=abs_path, sync_local=sync_local, concurrency=concurrency)
return (
s3_result["success"],
s3_result["msg"],
rel_path,
abs_path,
s3_result.get("downloaded", 0),
s3_result.get("uploaded", 0),
s3_result.get("skipped_unchanged", 0),
s3_result.get("failed", 0),
return io.NodeOutput(
s3_result["success"], s3_result["msg"], rel_path, abs_path,
s3_result.get("downloaded", 0), s3_result.get("uploaded", 0),
s3_result.get("skipped_unchanged", 0), s3_result.get("failed", 0),
)
@@ -3,94 +3,89 @@ from pathlib import Path
from inopyutils import ino_is_err, InoUtilHelper
import folder_paths
from comfy_api.latest import IO
from comfy_api.latest import io, IO
from .s3_helper import S3Helper, S3_EMPTY_CONFIG_STRING
from ..node_helper import any_type, PARENT_FOLDER_OPTIONS, resolve_comfy_path
from ..node_helper import PARENT_FOLDER_OPTIONS, resolve_comfy_path
class InoS3UploadAudio:
class InoS3UploadAudio(io.ComfyNode):
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"execute": (any_type,),
"enabled": ("BOOLEAN", {"default": True, "label_off": "OFF", "label_on": "ON"}),
"audio": ("AUDIO",),
"s3_key": ("STRING", {"default": ""}),
"parent_folder": (PARENT_FOLDER_OPTIONS, {"default": "output"}),
"folder": ("STRING", {"default": ""}),
"filename": ("STRING", {"default": ""})
},
"optional": {
"s3_config": ("STRING", {"default": S3_EMPTY_CONFIG_STRING, "tooltip": "you can leave it empty and pass it with env vars"}),
"date_time_as_name": ("BOOLEAN", {"default": False}),
},
}
def define_schema(cls):
return io.Schema(
node_id="InoS3UploadAudio",
display_name="Ino S3 Upload Audio",
category="InoS3Helper",
description="Saves audio as MP3 locally then uploads it to S3.",
is_output_node=True,
inputs=[
io.AnyType.Input("execute"),
io.Boolean.Input("enabled", default=True, label_off="OFF", label_on="ON"),
io.Audio.Input("audio"),
io.String.Input("s3_key", default=""),
io.Combo.Input("parent_folder", options=PARENT_FOLDER_OPTIONS, default="output"),
io.String.Input("folder", default=""),
io.String.Input("filename", default=""),
io.String.Input("s3_config", default=S3_EMPTY_CONFIG_STRING, optional=True, tooltip="you can leave it empty and pass it with env vars"),
io.Boolean.Input("date_time_as_name", default=False, optional=True),
],
outputs=[
io.Audio.Output(display_name="audio"),
io.Boolean.Output(display_name="success"),
io.String.Output(display_name="message"),
io.String.Output(display_name="rel_path"),
io.String.Output(display_name="abs_path"),
io.String.Output(display_name="file_name"),
io.String.Output(display_name="s3_audio_path"),
],
)
RETURN_TYPES = ("AUDIO", "BOOLEAN", "STRING", "STRING", "STRING", "STRING", "STRING",)
RETURN_NAMES = ("audio", "success", "message", "rel_path", "abs_path", "file_name", "s3_audio_path",)
FUNCTION = "function"
OUTPUT_NODE = True
CATEGORY = "InoS3Helper"
async def function(self, execute, enabled, audio, s3_key, parent_folder, folder, filename, s3_config=None, date_time_as_name=False):
@classmethod
async def execute(cls, execute, enabled, audio, s3_key, parent_folder, folder, filename, s3_config=None, date_time_as_name=False) -> io.NodeOutput:
if not enabled:
return (audio, False, "", "", "", "", "",)
return io.NodeOutput(audio, False, "", "", "", "", "")
if not execute:
return (audio, False, "", "", "", "", "",)
return io.NodeOutput(audio, False, "", "", "", "", "")
if isinstance(filename, list):
if len(filename) == 1:
filename = str(filename[0])
else:
return (audio, False, "file name is list", "", "", "", "",)
elif isinstance(filename, str):
pass
else:
return (audio, False, "file name not string", "", "", "", "",)
return io.NodeOutput(audio, False, "file name is list", "", "", "", "")
elif not isinstance(filename, str):
return io.NodeOutput(audio, False, "file name not string", "", "", "", "")
validate_s3_key = S3Helper.validate_s3_key(s3_key)
if not validate_s3_key["success"]:
return (audio, False, validate_s3_key["msg"], "", "", "", "",)
return io.NodeOutput(audio, False, validate_s3_key["msg"], "", "", "", "")
if date_time_as_name:
filename = InoUtilHelper.get_date_time_utc_base64()
rel_path, abs_path = resolve_comfy_path(parent_folder, folder)
save_as = "mp3"
file = f"{filename}.{save_as}"
file = f"{filename}.mp3"
from comfy_extras.nodes_audio import SaveAudioMP3
audio_saver = SaveAudioMP3()
save_audio: IO.NodeOutput = audio_saver.execute(
audio=audio,
filename_prefix=filename,
format="mp3",
quality="128k"
)
save_audio = audio_saver.execute(audio=audio, filename_prefix=filename, format="mp3", quality="128k")
try:
saved_filename = save_audio.ui.as_dict()["audio"][0]["filename"]
except:
return (audio, False, "Audio saved, but failed to get filename", rel_path, abs_path, "", "",)
return io.NodeOutput(audio, False, "Audio saved, but failed to get filename", rel_path, abs_path, "", "")
parent_path = folder_paths.get_output_directory()
full_path:Path = Path(parent_path) / saved_filename
full_path = Path(parent_path) / saved_filename
s3_instance = S3Helper.get_instance(s3_config)
if ino_is_err(s3_instance):
return (audio, False, s3_instance["msg"], rel_path, abs_path, "", "",)
return io.NodeOutput(audio, False, s3_instance["msg"], rel_path, abs_path, "", "")
s3_instance = s3_instance["instance"]
s3_full_key = f"{s3_key.rstrip('/')}/{saved_filename}"
s3_result = await s3_instance.upload_file(
s3_key=s3_full_key,
local_file_path=str(full_path),
)
s3_result = await s3_instance.upload_file(s3_key=s3_full_key, local_file_path=str(full_path))
if not s3_result["success"]:
return (audio, False, s3_result["msg"], rel_path, abs_path, "", "",)
return io.NodeOutput(audio, False, s3_result["msg"], rel_path, abs_path, "", "")
return (audio, True, "Success", rel_path, abs_path, file, s3_full_key, )
return io.NodeOutput(audio, True, "Success", rel_path, abs_path, file, s3_full_key)
@@ -1,66 +1,66 @@
import os
from pathlib import Path
from inopyutils import ino_ok, ino_err, ino_is_err
from inopyutils import ino_is_err
import folder_paths
from comfy_api.latest import io
from .s3_helper import S3Helper, S3_EMPTY_CONFIG_STRING
from ..node_helper import any_type, PARENT_FOLDER_OPTIONS, resolve_comfy_path
from ..node_helper import PARENT_FOLDER_OPTIONS, resolve_comfy_path
class InoS3UploadFile:
class InoS3UploadFile(io.ComfyNode):
@classmethod
def INPUT_TYPES(s):
return {
"required":{
"execute": (any_type,),
"enabled": ("BOOLEAN", {"default": True, "label_off": "OFF", "label_on": "ON"}),
"s3_key": ("STRING", {"default": ""}),
"parent_folder": (PARENT_FOLDER_OPTIONS,),
"folder": ("STRING", {"default": ""}),
"filename": ("STRING", {"default": ""}),
"delete_local": ("BOOLEAN", {"default": True}),
},
"optional": {
"s3_config": ("STRING", {"default": S3_EMPTY_CONFIG_STRING , "tooltip": "you can leave it empty and pass it with env vars"}),
"bucket_name": ("STRING", {"default": "default"}),
}
}
def define_schema(cls):
return io.Schema(
node_id="InoS3UploadFile",
display_name="Ino S3 Upload File",
category="InoS3Helper",
description="Uploads a local file to S3 with optional local deletion.",
is_output_node=True,
inputs=[
io.AnyType.Input("execute"),
io.Boolean.Input("enabled", default=True, label_off="OFF", label_on="ON"),
io.String.Input("s3_key", default=""),
io.Combo.Input("parent_folder", options=PARENT_FOLDER_OPTIONS),
io.String.Input("folder", default=""),
io.String.Input("filename", default=""),
io.Boolean.Input("delete_local", default=True),
io.String.Input("s3_config", default=S3_EMPTY_CONFIG_STRING, optional=True, tooltip="you can leave it empty and pass it with env vars"),
io.String.Input("bucket_name", default="default", optional=True),
],
outputs=[
io.Boolean.Output(display_name="success"),
io.String.Output(display_name="message"),
io.String.Output(display_name="rel_path"),
io.String.Output(display_name="abs_path"),
],
)
CATEGORY = "InoS3Helper"
RETURN_TYPES = ("BOOLEAN", "STRING", "STRING", "STRING", )
RETURN_NAMES = ("success", "message", "rel_path", "abs_path", )
FUNCTION = "function"
OUTPUT_NODE = True
async def function(self, execute, enabled:bool, s3_key:str, parent_folder:str, folder:str, filename:str, delete_local:bool, s3_config:str=None, bucket_name:str=None):
@classmethod
async def execute(cls, execute, enabled, s3_key, parent_folder, folder, filename, delete_local, s3_config=None, bucket_name=None) -> io.NodeOutput:
if not enabled:
return (False, "not enabled", "", "",)
return io.NodeOutput(False, "not enabled", "", "")
if not execute:
return (False, "execute empty", "", "", )
return io.NodeOutput(False, "execute empty", "", "")
validate_s3_key = S3Helper.validate_s3_key(s3_key)
if not validate_s3_key["success"]:
return (False, validate_s3_key["msg"], "", "", )
return io.NodeOutput(False, validate_s3_key["msg"], "", "")
rel_path, abs_path = resolve_comfy_path(parent_folder, folder, filename)
local_upload_path = Path(abs_path)
validate_local_path = S3Helper.validate_local_path(local_upload_path)
validate_local_path = S3Helper.validate_local_path(Path(abs_path))
if not validate_local_path["success"]:
return (False, validate_local_path["msg"], rel_path, abs_path, )
return io.NodeOutput(False, validate_local_path["msg"], rel_path, abs_path)
s3_instance = S3Helper.get_instance(s3_config)
if ino_is_err(s3_instance):
return (False, s3_instance["msg"], rel_path, abs_path, )
return io.NodeOutput(False, s3_instance["msg"], rel_path, abs_path)
s3_instance = s3_instance["instance"]
s3_result = await s3_instance.upload_file(
s3_key=s3_key,
local_file_path=abs_path,
)
s3_result = await s3_instance.upload_file(s3_key=s3_key, local_file_path=abs_path)
if s3_result["success"] and delete_local:
os.remove(abs_path)
return (s3_result["success"], s3_result["msg"], rel_path, abs_path, )
return io.NodeOutput(s3_result["success"], s3_result["msg"], rel_path, abs_path)
@@ -1,70 +1,71 @@
import os
import shutil
from pathlib import Path
import folder_paths
from inopyutils import ino_is_err
from inopyutils import ino_ok, ino_err, ino_is_err
from comfy_api.latest import io
from .s3_helper import S3Helper, S3_EMPTY_CONFIG_STRING
from ..node_helper import any_type, PARENT_FOLDER_OPTIONS, resolve_comfy_path
from ..node_helper import PARENT_FOLDER_OPTIONS, resolve_comfy_path
class InoS3UploadFolder:
class InoS3UploadFolder(io.ComfyNode):
@classmethod
def INPUT_TYPES(s):
return {
"required":{
"execute": (any_type,),
"enabled": ("BOOLEAN", {"default": True, "label_off": "OFF", "label_on": "ON"}),
"s3_key": ("STRING", {"default": ""}),
"parent_folder": (PARENT_FOLDER_OPTIONS, ),
"folder": ("STRING", {"default": ""}),
"delete_local": ("BOOLEAN", {"default": True}),
},
"optional": {
"s3_config": ("STRING", {"default": S3_EMPTY_CONFIG_STRING, "tooltip": "you can leave it empty and pass it with env vars"}),
"bucket_name": ("STRING", {"default": "default"}),
"max_concurrent": ("INT", {"default": 5, "min": 1, "max": 10}),
"verify_with_s3": ("BOOLEAN", {"default": False}),
}
}
def define_schema(cls):
return io.Schema(
node_id="InoS3UploadFolder",
display_name="Ino S3 Upload Folder",
category="InoS3Helper",
description="Uploads an entire local folder to S3 with concurrent uploads and optional verification.",
is_output_node=True,
inputs=[
io.AnyType.Input("execute"),
io.Boolean.Input("enabled", default=True, label_off="OFF", label_on="ON"),
io.String.Input("s3_key", default=""),
io.Combo.Input("parent_folder", options=PARENT_FOLDER_OPTIONS),
io.String.Input("folder", default=""),
io.Boolean.Input("delete_local", default=True),
io.String.Input("s3_config", default=S3_EMPTY_CONFIG_STRING, optional=True, tooltip="you can leave it empty and pass it with env vars"),
io.String.Input("bucket_name", default="default", optional=True),
io.Int.Input("max_concurrent", default=5, min=1, max=10, optional=True),
io.Boolean.Input("verify_with_s3", default=False, optional=True),
],
outputs=[
io.Boolean.Output(display_name="success"),
io.String.Output(display_name="message"),
io.String.Output(display_name="rel_path"),
io.String.Output(display_name="abs_path"),
io.Int.Output(display_name="total_files"),
io.Int.Output(display_name="uploaded_successfully"),
io.Int.Output(display_name="failed_uploads"),
io.String.Output(display_name="errors"),
],
)
CATEGORY = "InoS3Helper"
RETURN_TYPES = ("BOOLEAN", "STRING", "STRING", "STRING", "INT", "INT", "INT", "STRING", )
RETURN_NAMES = ("success", "message", "rel_path", "abs_path", "total_files", "uploaded_successfully", "failed_uploads", "errors", )
FUNCTION = "function"
OUTPUT_NODE = True
async def function(self, execute, enabled, s3_key, parent_folder, folder, delete_local, s3_config=None, bucket_name=None, max_concurrent=5, verify_with_s3=False):
@classmethod
async def execute(cls, execute, enabled, s3_key, parent_folder, folder, delete_local, s3_config=None, bucket_name=None, max_concurrent=5, verify_with_s3=False) -> io.NodeOutput:
if not enabled:
return (False, "", "", "", 0, 0, 0, "", )
return io.NodeOutput(False, "", "", "", 0, 0, 0, "")
if not execute:
return (False, "", "", "", 0, 0, 0, "", )
return io.NodeOutput(False, "", "", "", 0, 0, 0, "")
validate_s3_key = S3Helper.validate_s3_key(s3_key)
if not validate_s3_key["success"]:
return (False, validate_s3_key["msg"], "", "", 0, 0, 0, "", )
return io.NodeOutput(False, validate_s3_key["msg"], "", "", 0, 0, 0, "")
rel_path, abs_path = resolve_comfy_path(parent_folder, folder)
local_upload_path = Path(abs_path)
validate_local_path = S3Helper.validate_local_path(local_upload_path)
validate_local_path = S3Helper.validate_local_path(Path(abs_path))
if not validate_local_path["success"]:
return (False, validate_local_path["msg"], rel_path, abs_path, 0, 0, 0, "", )
return io.NodeOutput(False, validate_local_path["msg"], rel_path, abs_path, 0, 0, 0, "")
s3_instance = S3Helper.get_instance(s3_config)
if ino_is_err(s3_instance):
return (False, s3_instance["msg"], rel_path, abs_path, 0, 0, 0, "", )
return io.NodeOutput(False, s3_instance["msg"], rel_path, abs_path, 0, 0, 0, "")
s3_instance = s3_instance["instance"]
s3_result = await s3_instance.upload_folder(
s3_folder_key=s3_key,
local_folder_path=abs_path,
max_concurrent=max_concurrent,
verify=verify_with_s3
)
s3_result = await s3_instance.upload_folder(s3_folder_key=s3_key, local_folder_path=abs_path, max_concurrent=max_concurrent, verify=verify_with_s3)
if s3_result["success"] and delete_local:
shutil.rmtree(local_upload_path)
shutil.rmtree(Path(abs_path))
return (s3_result["success"], s3_result["msg"], rel_path, abs_path, s3_result["total_files"], s3_result["uploaded_successfully"], s3_result["failed_uploads"], str(s3_result["errors"]), )
return io.NodeOutput(s3_result["success"], s3_result["msg"], rel_path, abs_path, s3_result["total_files"], s3_result["uploaded_successfully"], s3_result["failed_uploads"], str(s3_result["errors"]))
@@ -1,62 +1,69 @@
import os
from pathlib import Path
from datetime import datetime
from PIL import Image, ImageOps, ImageSequence
from PIL import Image
from PIL.PngImagePlugin import PngInfo
import numpy as np
from inopyutils import InoJsonHelper, ino_ok, ino_err, ino_is_err, InoUtilHelper
from inopyutils import InoJsonHelper, ino_is_err, InoUtilHelper
import folder_paths
from comfy.cli_args import args
from comfy_api.latest import io
from .s3_helper import S3Helper, S3_EMPTY_CONFIG_STRING
from ..node_helper import any_type, PARENT_FOLDER_OPTIONS, resolve_comfy_path
from ..node_helper import PARENT_FOLDER_OPTIONS, resolve_comfy_path
class InoS3UploadImage:
class InoS3UploadImage(io.ComfyNode):
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"execute": (any_type,),
"enabled": ("BOOLEAN", {"default": True, "label_off": "OFF", "label_on": "ON"}),
"images": ("IMAGE",),
"s3_key": ("STRING", {"default": ""}),
"parent_folder": (PARENT_FOLDER_OPTIONS, {"default": "temp"}),
"folder": ("STRING", {"default": ""}),
"filename": ("STRING", {"default": ""})
},
"optional": {
"s3_config": ("STRING", {"default": S3_EMPTY_CONFIG_STRING, "tooltip": "you can leave it empty and pass it with env vars"}),
"compress_level": ("INT", {"default": 4, "min": 1, "max": 9}),
"date_time_as_name": ("BOOLEAN", {"default": False}),
},
}
def define_schema(cls):
return io.Schema(
node_id="InoS3UploadImage",
display_name="Ino S3 Upload Image",
category="InoS3Helper",
description="Saves images locally as PNG then uploads them to S3.",
is_output_node=True,
inputs=[
io.AnyType.Input("execute"),
io.Boolean.Input("enabled", default=True, label_off="OFF", label_on="ON"),
io.Image.Input("images"),
io.String.Input("s3_key", default=""),
io.Combo.Input("parent_folder", options=PARENT_FOLDER_OPTIONS, default="temp"),
io.String.Input("folder", default=""),
io.String.Input("filename", default=""),
io.String.Input("s3_config", default=S3_EMPTY_CONFIG_STRING, optional=True, tooltip="you can leave it empty and pass it with env vars"),
io.Int.Input("compress_level", default=4, min=1, max=9, optional=True),
io.Boolean.Input("date_time_as_name", default=False, optional=True),
],
outputs=[
io.Image.Output(display_name="images"),
io.Boolean.Output(display_name="success"),
io.String.Output(display_name="message"),
io.String.Output(display_name="rel_path"),
io.String.Output(display_name="abs_path"),
io.String.Output(display_name="file_names"),
io.String.Output(display_name="s3_image_paths"),
],
)
RETURN_TYPES = ("IMAGE", "BOOLEAN", "STRING", "STRING", "STRING", "STRING", "STRING",)
RETURN_NAMES = ("images", "success", "message", "rel_path", "abs_path", "file_names", "s3_image_paths",)
FUNCTION = "function"
OUTPUT_NODE = True
CATEGORY = "InoS3Helper"
async def function(self, execute, enabled, images, s3_key, parent_folder, folder, filename, s3_config=None, compress_level=4, date_time_as_name=False):
@classmethod
async def execute(cls, execute, enabled, images, s3_key, parent_folder, folder, filename, s3_config=None, compress_level=4, date_time_as_name=False) -> io.NodeOutput:
if not enabled:
return (images, False, "", "", "", "", "",)
return io.NodeOutput(images, False, "", "", "", "", "")
if not execute:
return (images, False, "", "", "", "", "",)
return io.NodeOutput(images, False, "", "", "", "", "")
validate_s3_key = S3Helper.validate_s3_key(s3_key)
if not validate_s3_key["success"]:
return (images, False, validate_s3_key["msg"], "", "", "", "")
return io.NodeOutput(images, False, validate_s3_key["msg"], "", "", "", "")
if date_time_as_name:
filename = InoUtilHelper.get_date_time_utc_base64()
rel_path, abs_path = resolve_comfy_path(parent_folder, folder)
full_output_folder, file_prefix, counter, subfolder, filename_prefix = folder_paths.get_save_image_path(filename, abs_path, images[0].shape[1], images[0].shape[0])
results:dict = {}
full_output_folder, file_prefix, counter, subfolder, _ = folder_paths.get_save_image_path(filename, abs_path, images[0].shape[1], images[0].shape[0])
results = {}
for (batch_number, image) in enumerate(images):
i = 255. * image.cpu().numpy()
img = Image.fromarray(np.clip(i, 0, 255).astype(np.uint8))
@@ -69,31 +76,22 @@ class InoS3UploadImage:
file_w_ext = file + ".png"
full_path = os.path.join(full_output_folder, file_w_ext)
img.save(full_path, pnginfo=metadata, compress_level=compress_level)
results[batch_number] = {
"filename": file,
"filename_w_ext": file_w_ext,
"full_path": full_path,
}
results[batch_number] = {"filename": file, "filename_w_ext": file_w_ext, "full_path": full_path}
counter += 1
img.close()
s3_instance = S3Helper.get_instance(s3_config)
if ino_is_err(s3_instance):
return (images, False, s3_instance["msg"], rel_path, abs_path, "", "")
return io.NodeOutput(images, False, s3_instance["msg"], rel_path, abs_path, "", "")
s3_instance = s3_instance["instance"]
for index in results:
s3_full_key = s3_key + "/" + results[index]["filename_w_ext"]
s3_result = await s3_instance.upload_file(
s3_key=s3_full_key,
local_file_path=results[index]["full_path"],
)
s3_result = await s3_instance.upload_file(s3_key=s3_full_key, local_file_path=results[index]["full_path"])
results[index]["s3_success"] = s3_result["success"]
results[index]["s3_msg"] = s3_result["msg"]
results[index]["s3_key"] = s3_full_key
result_str = InoJsonHelper.dict_to_string(results)['data']
final_success = True
final_message = ""
for index in results:
@@ -103,12 +101,9 @@ class InoS3UploadImage:
break
if not final_success:
return (images, final_success, final_message, rel_path, abs_path, "", "")
return io.NodeOutput(images, final_success, final_message, rel_path, abs_path, "", "")
s3_paths = []
file_names = []
for index in results:
s3_paths.append(results[index]["s3_key"])
file_names.append(results[index]["filename"])
s3_paths = [results[i]["s3_key"] for i in results]
file_names = [results[i]["filename"] for i in results]
return (images, True, "Success", rel_path, abs_path, str(file_names), str(s3_paths), )
return io.NodeOutput(images, True, "Success", rel_path, abs_path, str(file_names), str(s3_paths))
@@ -1,75 +1,76 @@
import os
from pathlib import Path
from datetime import datetime
from PIL import Image, ImageOps, ImageSequence
from PIL.PngImagePlugin import PngInfo
import numpy as np
from inopyutils import InoJsonHelper, InoFileHelper, ino_ok, ino_err, ino_is_err, InoUtilHelper
from inopyutils import InoJsonHelper, InoFileHelper, ino_is_err, InoUtilHelper
import folder_paths
from comfy.cli_args import args
from comfy_api.latest import io
from .s3_helper import S3Helper, S3_EMPTY_CONFIG_STRING
from ..node_helper import any_type, PARENT_FOLDER_OPTIONS, resolve_comfy_path
from ..node_helper import PARENT_FOLDER_OPTIONS, resolve_comfy_path
class InoS3UploadString:
class InoS3UploadString(io.ComfyNode):
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"execute": (any_type,),
"enabled": ("BOOLEAN", {"default": True, "label_off": "OFF", "label_on": "ON"}),
"string": ("STRING",),
"save_as": (["txt", "json", "ini"], ),
"s3_key": ("STRING", {"default": ""}),
"parent_folder": (PARENT_FOLDER_OPTIONS, {"default": "temp"}),
"folder": ("STRING", {"default": ""}),
"filename": ("STRING", {"default": ""})
},
"optional": {
"s3_config": ("STRING", {"default": S3_EMPTY_CONFIG_STRING, "tooltip": "you can leave it empty and pass it with env vars"}),
"date_time_as_name": ("BOOLEAN", {"default": False}),
},
}
def define_schema(cls):
return io.Schema(
node_id="InoS3UploadString",
display_name="Ino S3 Upload String",
category="InoS3Helper",
description="Saves a string as a file (txt/json/ini) locally then uploads it to S3.",
is_output_node=True,
inputs=[
io.AnyType.Input("execute"),
io.Boolean.Input("enabled", default=True, label_off="OFF", label_on="ON"),
io.String.Input("string"),
io.Combo.Input("save_as", options=["txt", "json", "ini"]),
io.String.Input("s3_key", default=""),
io.Combo.Input("parent_folder", options=PARENT_FOLDER_OPTIONS, default="temp"),
io.String.Input("folder", default=""),
io.String.Input("filename", default=""),
io.String.Input("s3_config", default=S3_EMPTY_CONFIG_STRING, optional=True, tooltip="you can leave it empty and pass it with env vars"),
io.Boolean.Input("date_time_as_name", default=False, optional=True),
],
outputs=[
io.String.Output(display_name="string"),
io.Boolean.Output(display_name="success"),
io.String.Output(display_name="message"),
io.String.Output(display_name="rel_path"),
io.String.Output(display_name="abs_path"),
io.String.Output(display_name="file_name"),
io.String.Output(display_name="s3_string_path"),
],
)
RETURN_TYPES = ("STRING", "BOOLEAN", "STRING", "STRING", "STRING", "STRING", "STRING",)
RETURN_NAMES = ("STRING", "success", "message", "rel_path", "abs_path", "file_name", "s3_string_path",)
FUNCTION = "function"
OUTPUT_NODE = True
CATEGORY = "InoS3Helper"
async def function(self, execute, enabled, string, save_as, s3_key, parent_folder, folder, filename, s3_config=None, date_time_as_name=False):
@classmethod
async def execute(cls, execute, enabled, string, save_as, s3_key, parent_folder, folder, filename, s3_config=None, date_time_as_name=False) -> io.NodeOutput:
if not enabled:
return (string, False, "", "", "", "", "",)
return io.NodeOutput(string, False, "", "", "", "", "")
if not execute:
return (string, False, "", "", "", "", "",)
return io.NodeOutput(string, False, "", "", "", "", "")
if isinstance(filename, list):
if len(filename) == 1:
filename = str(filename[0])
else:
return (string, False, "file name is list", "", "", "", "",)
elif isinstance(filename, str):
pass
else:
return (string, False, "file name not string", "", "", "", "",)
return io.NodeOutput(string, False, "file name is list", "", "", "", "")
elif not isinstance(filename, str):
return io.NodeOutput(string, False, "file name not string", "", "", "", "")
validate_s3_key = S3Helper.validate_s3_key(s3_key)
if not validate_s3_key["success"]:
return (string, False, validate_s3_key["msg"], "", "", "", "",)
return io.NodeOutput(string, False, validate_s3_key["msg"], "", "", "", "")
if date_time_as_name:
filename = InoUtilHelper.get_date_time_utc_base64()
rel_path, abs_path = resolve_comfy_path(parent_folder, folder)
full_output_folder, file_prefix, counter, subfolder, filename_prefix = folder_paths.get_save_image_path(filename, abs_path, 0, 0)
full_output_folder, file_prefix, counter, subfolder, _ = folder_paths.get_save_image_path(filename, abs_path, 0, 0)
filename_with_batch_num = file_prefix.replace("%batch_num%", "0")
file = f"{filename_with_batch_num}_{counter:05}"
file_w_ext= f"{file}.{save_as}"
file_w_ext = f"{file}.{save_as}"
full_path = os.path.join(full_output_folder, file_w_ext)
if save_as == "json":
@@ -78,19 +79,16 @@ class InoS3UploadString:
save_file = await InoFileHelper.save_string_as_file(string, full_path)
if not save_file["success"]:
return (string, False, save_file["msg"], rel_path, abs_path, "", "", )
return io.NodeOutput(string, False, save_file["msg"], rel_path, abs_path, "", "")
s3_instance = S3Helper.get_instance(s3_config)
if ino_is_err(s3_instance):
return (string, False, s3_instance["msg"], rel_path, abs_path, "", "", )
return io.NodeOutput(string, False, s3_instance["msg"], rel_path, abs_path, "", "")
s3_instance = s3_instance["instance"]
s3_full_key = f"{s3_key.rstrip('/')}/{filename}.{save_as}"
s3_result = await s3_instance.upload_file(
s3_key=s3_full_key,
local_file_path=full_path,
)
s3_result = await s3_instance.upload_file(s3_key=s3_full_key, local_file_path=full_path)
if not s3_result["success"]:
return (string, False, s3_result["msg"], rel_path, abs_path, "", "",)
return io.NodeOutput(string, False, s3_result["msg"], rel_path, abs_path, "", "")
return (string, True, "Success", rel_path, abs_path, file_w_ext, s3_full_key, )
return io.NodeOutput(string, True, "Success", rel_path, abs_path, file_w_ext, s3_full_key)
@@ -1,56 +1,58 @@
import os
from pathlib import Path
from datetime import datetime
from PIL import Image, ImageOps, ImageSequence
from PIL.PngImagePlugin import PngInfo
import numpy as np
from inopyutils import InoJsonHelper, ino_ok, ino_err, ino_is_err, InoUtilHelper
from inopyutils import ino_is_err, InoUtilHelper
import folder_paths
from comfy.cli_args import args
from comfy_api.latest import ComfyExtension, io, ui, Input, InputImpl, Types
from comfy_api.latest import io, Input, Types
from .s3_helper import S3Helper, S3_EMPTY_CONFIG_STRING
from ..node_helper import any_type, PARENT_FOLDER_OPTIONS, resolve_comfy_path
from ..node_helper import PARENT_FOLDER_OPTIONS, resolve_comfy_path
class InoS3UploadVideo:
class InoS3UploadVideo(io.ComfyNode):
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"execute": (any_type,),
"enabled": ("BOOLEAN", {"default": True, "label_off": "OFF", "label_on": "ON"}),
"video": ("VIDEO",),
"s3_key": ("STRING", {"default": ""}),
"parent_folder": (PARENT_FOLDER_OPTIONS, {"default": "temp"}),
"folder": ("STRING", {"default": ""}),
"filename": ("STRING", {"default": ""})
},
"optional": {
"s3_config": ("STRING", {"default": S3_EMPTY_CONFIG_STRING, "tooltip": "you can leave it empty and pass it with env vars"}),
"date_time_as_name": ("BOOLEAN", {"default": False}),
"video_format": (("mp4", "auto"), {}),
"video_codec": (("h264", "auto"), {}),
},
}
def define_schema(cls):
return io.Schema(
node_id="InoS3UploadVideo",
display_name="Ino S3 Upload Video",
category="InoS3Helper",
description="Saves video locally then uploads it to S3.",
is_output_node=True,
inputs=[
io.AnyType.Input("execute"),
io.Boolean.Input("enabled", default=True, label_off="OFF", label_on="ON"),
io.Video.Input("video"),
io.String.Input("s3_key", default=""),
io.Combo.Input("parent_folder", options=PARENT_FOLDER_OPTIONS, default="temp"),
io.String.Input("folder", default=""),
io.String.Input("filename", default=""),
io.String.Input("s3_config", default=S3_EMPTY_CONFIG_STRING, optional=True, tooltip="you can leave it empty and pass it with env vars"),
io.Boolean.Input("date_time_as_name", default=False, optional=True),
io.Combo.Input("video_format", options=["mp4", "auto"], optional=True),
io.Combo.Input("video_codec", options=["h264", "auto"], optional=True),
],
outputs=[
io.Video.Output(display_name="video"),
io.Boolean.Output(display_name="success"),
io.String.Output(display_name="message"),
io.String.Output(display_name="rel_path"),
io.String.Output(display_name="abs_path"),
io.String.Output(display_name="file_name"),
io.String.Output(display_name="s3_video_path"),
],
)
RETURN_TYPES = ("Video", "BOOLEAN", "STRING", "STRING", "STRING", "STRING", "STRING",)
RETURN_NAMES = ("video", "success", "message", "rel_path", "abs_path", "file_name", "s3_video_path",)
FUNCTION = "function"
OUTPUT_NODE = True
CATEGORY = "InoS3Helper"
async def function(self, execute, enabled, video: Input.Video, s3_key, parent_folder, folder, filename, s3_config=None, date_time_as_name=False, video_format="mp4", video_codec="h264"):
@classmethod
async def execute(cls, execute, enabled, video: Input.Video, s3_key, parent_folder, folder, filename, s3_config=None, date_time_as_name=False, video_format="mp4", video_codec="h264") -> io.NodeOutput:
if not enabled:
return (video, False, "", "", "", "", "",)
return io.NodeOutput(video, False, "", "", "", "", "")
if not execute:
return (video, False, "", "", "", "", "",)
return io.NodeOutput(video, False, "", "", "", "", "")
validate_s3_key = S3Helper.validate_s3_key(s3_key)
if not validate_s3_key["success"]:
return (video, False, validate_s3_key["msg"], "", "", "", "")
return io.NodeOutput(video, False, validate_s3_key["msg"], "", "", "", "")
if date_time_as_name:
filename = InoUtilHelper.get_date_time_utc_base64()
@@ -58,36 +60,19 @@ class InoS3UploadVideo:
rel_path, abs_path = resolve_comfy_path(parent_folder, folder)
width, height = video.get_dimensions()
full_output_folder, file_prefix, counter, subfolder, filename_prefix = folder_paths.get_save_image_path(
filename,
abs_path,
width,
height
)
saved_metadata = None
full_output_folder, file_prefix, counter, subfolder, _ = folder_paths.get_save_image_path(filename, abs_path, width, height)
filename_w_ext = f"{file_prefix}.{Types.VideoContainer.get_extension(video_format)}"
file_path = os.path.join(full_output_folder, filename_w_ext)
video.save_to(
file_path,
format=Types.VideoContainer(video_format),
codec=video_codec,
metadata=saved_metadata
)
video.save_to(file_path, format=Types.VideoContainer(video_format), codec=Types.VideoCodec(video_codec))
s3_instance = S3Helper.get_instance(s3_config)
if ino_is_err(s3_instance):
return (video, False, s3_instance["msg"], rel_path, abs_path, "", "")
return io.NodeOutput(video, False, s3_instance["msg"], rel_path, abs_path, "", "")
s3_instance = s3_instance["instance"]
s3_full_key = s3_key + "/" + filename_w_ext
s3_result = await s3_instance.upload_file(
s3_key=s3_full_key,
local_file_path=file_path,
)
if not s3_result["success"]:
return (video, s3_result["success"], s3_result["msg"], rel_path, abs_path, filename, s3_full_key,)
s3_result = await s3_instance.upload_file(s3_key=s3_full_key, local_file_path=file_path)
return (video, s3_result["success"], s3_result["msg"], rel_path, abs_path, filename, s3_full_key,)
return io.NodeOutput(video, s3_result["success"], s3_result["msg"], rel_path, abs_path, filename, s3_full_key)
@@ -2,70 +2,66 @@ from pathlib import Path
from inopyutils import ino_is_err
import folder_paths
from comfy_api.latest import io
from .s3_helper import S3Helper, S3_EMPTY_CONFIG_STRING
from ..node_helper import any_type, PARENT_FOLDER_OPTIONS, resolve_comfy_path
from ..node_helper import PARENT_FOLDER_OPTIONS, resolve_comfy_path
class InoS3VerifyFile:
class InoS3VerifyFile(io.ComfyNode):
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"execute": (any_type,),
"enabled": ("BOOLEAN", {"default": True, "label_off": "OFF", "label_on": "ON"}),
"s3_key": ("STRING", {"default": ""}),
"parent_folder": (PARENT_FOLDER_OPTIONS,),
"folder": ("STRING", {"default": ""}),
"filename": ("STRING", {"default": ""}),
},
"optional": {
"s3_config": ("STRING", {"default": S3_EMPTY_CONFIG_STRING, "tooltip": "you can leave it empty and pass it with env vars"}),
"use_md5": ("BOOLEAN", {"default": False}),
"use_sha256": ("BOOLEAN", {"default": False}),
}
}
def define_schema(cls):
return io.Schema(
node_id="InoS3VerifyFile",
display_name="Ino S3 Verify File",
category="InoS3Helper",
description="Verifies a local file exists in S3 and optionally checks integrity via hash.",
inputs=[
io.AnyType.Input("execute"),
io.Boolean.Input("enabled", default=True, label_off="OFF", label_on="ON"),
io.String.Input("s3_key", default=""),
io.Combo.Input("parent_folder", options=PARENT_FOLDER_OPTIONS),
io.String.Input("folder", default=""),
io.String.Input("filename", default=""),
io.String.Input("s3_config", default=S3_EMPTY_CONFIG_STRING, optional=True, tooltip="you can leave it empty and pass it with env vars"),
io.Boolean.Input("use_md5", default=False, optional=True),
io.Boolean.Input("use_sha256", default=False, optional=True),
],
outputs=[
io.Boolean.Output(display_name="success"),
io.String.Output(display_name="message"),
io.String.Output(display_name="rel_path"),
io.String.Output(display_name="abs_path"),
io.Boolean.Output(display_name="exists_remote"),
io.Boolean.Output(display_name="sizes_match"),
],
)
CATEGORY = "InoS3Helper"
RETURN_TYPES = ("BOOLEAN", "STRING", "STRING", "STRING", "BOOLEAN", "BOOLEAN",)
RETURN_NAMES = ("success", "message", "rel_path", "abs_path", "exists_remote", "sizes_match",)
FUNCTION = "function"
async def function(self, execute, enabled, s3_key, parent_folder, folder, filename, s3_config=None, use_md5=False, use_sha256=False):
@classmethod
async def execute(cls, execute, enabled, s3_key, parent_folder, folder, filename, s3_config=None, use_md5=False, use_sha256=False) -> io.NodeOutput:
if not enabled:
return (False, "not enabled", "", "", False, False,)
return io.NodeOutput(False, "not enabled", "", "", False, False)
if not execute:
return (False, "execute empty", "", "", False, False,)
return io.NodeOutput(False, "execute empty", "", "", False, False)
validate_s3_key = S3Helper.validate_s3_key(s3_key)
if not validate_s3_key["success"]:
return (False, validate_s3_key["msg"], "", "", False, False,)
return io.NodeOutput(False, validate_s3_key["msg"], "", "", False, False)
rel_path, abs_path = resolve_comfy_path(parent_folder, folder, filename)
local_file_path = Path(abs_path)
validate_local_path = S3Helper.validate_local_path(local_file_path)
validate_local_path = S3Helper.validate_local_path(Path(abs_path))
if not validate_local_path["success"]:
return (False, validate_local_path["msg"], rel_path, abs_path, False, False,)
return io.NodeOutput(False, validate_local_path["msg"], rel_path, abs_path, False, False)
s3_instance = S3Helper.get_instance(s3_config)
if ino_is_err(s3_instance):
return (False, s3_instance["msg"], rel_path, abs_path, False, False,)
return io.NodeOutput(False, s3_instance["msg"], rel_path, abs_path, False, False)
s3_instance = s3_instance["instance"]
s3_result = await s3_instance.verify_file(
local_file_path=abs_path,
s3_key=s3_key,
use_md5=use_md5,
use_sha256=use_sha256,
)
s3_result = await s3_instance.verify_file(local_file_path=abs_path, s3_key=s3_key, use_md5=use_md5, use_sha256=use_sha256)
return (
s3_result["success"],
s3_result["msg"],
rel_path,
abs_path,
s3_result.get("exists_remote", False),
s3_result.get("sizes_match", False),
return io.NodeOutput(
s3_result["success"], s3_result["msg"], rel_path, abs_path,
s3_result.get("exists_remote", False), s3_result.get("sizes_match", False),
)