Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
86 commits
Select commit Hold shift + click to select a range
3f742ed
Merge pull request #5 from myshell-ai/Convert-to-ShellAgent-with-defa…
shanexi Oct 21, 2024
be2725c
add dependency checker
wl-zhao Oct 21, 2024
2eea258
Merge branch 'main' of github.com:myshell-ai/ComfyUI-ShellAgent-Plugi…
wl-zhao Oct 21, 2024
bf8f352
skip when .git is not found
wl-zhao Oct 21, 2024
b3042fd
fix bug
wl-zhao Oct 22, 2024
fc1f9af
add new output nodes
wl-zhao Oct 22, 2024
00fb0b9
handle model_searcher fail
wl-zhao Oct 23, 2024
26e57ef
Update dependency_checker.py
yuxumin Oct 23, 2024
15c7d7f
Optimize number input convert
shanexi Oct 24, 2024
45e7cac
use folder_path to find the models
wl-zhao Oct 25, 2024
a4d96af
add blacklist, update model search
wl-zhao Oct 25, 2024
232bc67
handle relative path
wl-zhao Oct 25, 2024
c0da8f9
update get_full_path_or_rase
wl-zhao Oct 25, 2024
6666050
update node deps info
yuxumin Oct 25, 2024
2b19a13
addmap_legacy
wl-zhao Oct 26, 2024
5521822
Merge branch 'main' of https://github.com/myshell-ai/ComfyUI-ShellAge…
wl-zhao Oct 26, 2024
d2cfd99
Ouput convert
shanexi Oct 24, 2024
4fb7991
Convert to save video
shanexi Oct 27, 2024
9417b64
No need to connect image combo
shanexi Oct 27, 2024
752a0de
add gguf
wl-zhao Oct 27, 2024
710104d
Image input
shanexi Oct 27, 2024
1a826fa
update node_deps json
yuxumin Oct 27, 2024
3c8a5eb
Merge branch 'main' of https://github.com/myshell-ai/ComfyUI-ShellAge…
yuxumin Oct 27, 2024
134ccd3
fix dependencies deps
yuxumin Oct 27, 2024
ddad7b8
Update README.md
yuxumin Oct 27, 2024
ae8ed60
Save Image(s) on output connect pop menu
shanexi Oct 28, 2024
b825b62
Drag to connect output text float integer
shanexi Oct 28, 2024
cbd714d
Replace Load Image with ShellAgent Input Image
shanexi Oct 28, 2024
f9a3ce4
Replace and remove
shanexi Oct 28, 2024
20fcbb2
Fix duplicated drag menu item
shanexi Oct 28, 2024
fffc29f
Add missing convert output
shanexi Oct 28, 2024
344a886
add pypi version info
wl-zhao Oct 28, 2024
f28c2c6
Replace with default value assigned
shanexi Oct 28, 2024
3b5a9b5
fix file upload error
wl-zhao Oct 28, 2024
e47726e
Merge branch 'main' of https://github.com/myshell-ai/ComfyUI-ShellAge…
wl-zhao Oct 28, 2024
070fdb5
Merge pull request #7 from myshell-ai/6-add-more-convert
wl-zhao Oct 28, 2024
a9d07ba
hardcode hf packages
wl-zhao Oct 28, 2024
73bfa4e
add warning message when no inputs/outputs founded
wl-zhao Oct 28, 2024
4eb3e8b
add message details
wl-zhao Oct 29, 2024
ae29480
Update dependency_checker.py
yuxumin Oct 29, 2024
8e8d10b
update output video
wl-zhao Oct 30, 2024
deba76e
windows to linux path filenames
wl-zhao Oct 30, 2024
64ebfa4
Update output_image.py
wl-zhao Oct 30, 2024
34f8feb
add boolean; fix output nodes input type bugs; fix input_video enum v…
wl-zhao Nov 1, 2024
fb3e973
add boolean; fix output nodes input type bugs; fix input_video enum v…
wl-zhao Nov 1, 2024
c80f154
input video
wl-zhao Nov 1, 2024
0904716
fix input video bug
wl-zhao Nov 3, 2024
1faaad5
Update input_video.py
yuxumin Nov 3, 2024
1aa0fc1
revert input_video.py
yuxumin Nov 3, 2024
4c8e720
fix output video path error
wl-zhao Nov 4, 2024
24c17be
fix output video path error
wl-zhao Nov 4, 2024
4d548bf
support .sft for model suffix
yuxumin Nov 6, 2024
85e01a8
add tree_map for dependency checker
wl-zhao Nov 7, 2024
091d2ff
merge
wl-zhao Nov 7, 2024
42981b7
add validation for variable name
wl-zhao Nov 11, 2024
35b2700
add backtick
wl-zhao Nov 11, 2024
ffa4123
support skip model check for some nodes
wl-zhao Nov 12, 2024
d9d2001
improve abs_path of file dependency
wl-zhao Nov 14, 2024
9267390
add glob to search models, and raise error when no models founded / m…
wl-zhao Nov 14, 2024
27613ed
support models in nodes / skip configs / input image support mask
wl-zhao Nov 22, 2024
2d21584
add route to inspect version
wl-zhao Dec 3, 2024
623ea45
support input/output audio backend
wl-zhao Dec 10, 2024
676c406
Update node_deps_info.json
yuxumin Dec 12, 2024
f1135ac
Update node_deps_info.json
yuxumin Dec 12, 2024
7238c1f
fix error message when empty input image
wl-zhao Dec 13, 2024
07c0807
kMerge branch 'main' of https://github.com/myshell-ai/ComfyUI-ShellAg…
wl-zhao Dec 13, 2024
82f3a05
fix mask bug
wl-zhao Dec 13, 2024
604d349
support heif image
wl-zhao Dec 16, 2024
637bc88
pass validation when os.path.isfile
wl-zhao Dec 17, 2024
2310c33
add get mac_addr
wl-zhao Dec 18, 2024
37eb10c
add check_exist
wl-zhao Dec 18, 2024
580ac93
add mac_addr to check realy exist
wl-zhao Dec 18, 2024
681f716
update safe open image function
wl-zhao Dec 18, 2024
4e2bfd5
Update dependency_checker.py
wl-zhao Dec 30, 2024
04d33d8
add easydict
wl-zhao Jan 13, 2025
31f9503
Merge branch 'main' of https://github.com/myshell-ai/ComfyUI-ShellAge…
wl-zhao Jan 13, 2025
078b3f5
Add joint dependency for Easy use (ComfyUI_IPAdapter_plus)
yuxumin Jan 16, 2025
bc62e8a
feat: support input audio
shanexi Feb 12, 2025
0875713
Merge pull request #9 from myshell-ai/support-input-video
shanexi Feb 13, 2025
06163a0
i[date
Cherwayway Mar 10, 2025
33899c1
Merge pull request #10 from myshell-ai/fix_input_image
Cherwayway Mar 10, 2025
0599875
fix: input image error in new comfyui version
shanexi Mar 12, 2025
ced3e0d
remove image
Cherwayway Mar 12, 2025
4558c88
Merge pull request #11 from myshell-ai/fix-input-image-node-new-comfyui
Cherwayway Mar 12, 2025
1f16525
update
Cherwayway Mar 12, 2025
d27fafb
fix shellagent save audio for new version comfyui
Cherwayway May 22, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ To install, either:
- Input Image
- Input Float
- Input Integer
- Input Video

Each input node supports setting a default value and additional configuration options.

Expand All @@ -25,6 +26,9 @@ Each input node supports setting a default value and additional configuration op
- Save Image
- Save Images
- Save Video - VHS
- Output Text
- Output Float
- Output Integer

### Convert Widgets to ShellAgent Inputs

Expand Down
200 changes: 200 additions & 0 deletions comfy-nodes/input_audio.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
import folder_paths
import node_helpers

from PIL import Image, ImageOps, ImageSequence, ImageFile
import numpy as np
import torch
import os
import uuid
import tqdm
import torchaudio
import hashlib
from comfy_extras.nodes_audio import SaveAudio


class LoadAudio:
@classmethod
def INPUT_TYPES(s):
input_dir = folder_paths.get_input_directory()
files = folder_paths.filter_files_content_types(
os.listdir(input_dir), ["audio", "video"])
return {"required": {"audio": (sorted(files), {"audio_upload": True})}}

CATEGORY = "audio"

RETURN_TYPES = ("AUDIO", )
FUNCTION = "load"

def load(self, audio):
audio_path = folder_paths.get_annotated_filepath(audio)
waveform, sample_rate = torchaudio.load(audio_path)
audio = {"waveform": waveform.unsqueeze(0), "sample_rate": sample_rate}
return (audio, )

@classmethod
def IS_CHANGED(s, audio):
image_path = folder_paths.get_annotated_filepath(audio)
m = hashlib.sha256()
with open(image_path, 'rb') as f:
m.update(f.read())
return m.digest().hex()

@classmethod
def VALIDATE_INPUTS(s, audio):
if not folder_paths.exists_annotated_filepath(audio):
return "Invalid audio file: {}".format(audio)
return True


class ShellAgentPluginInputAudio:
@classmethod
def INPUT_TYPES(s):
input_dir = folder_paths.get_input_directory()
files = folder_paths.filter_files_content_types(
os.listdir(input_dir), ["audio", "video"])
return {
"required": {
"input_name": (
"STRING",
{"multiline": False, "default": "input_audio", "forceInput": False},
),
"default_value": (
sorted(files), {"audio_upload": True, "forceInput": False}
),
},
"optional": {
"description": (
"STRING",
{"multiline": True, "default": "", "forceInput": False},
),
}
}

RETURN_TYPES = ("AUDIO", )
FUNCTION = "load"

CATEGORY = "shellagent"

@classmethod
def validate(cls, **kwargs):
schema = {
"title": kwargs["input_name"],
"type": "string",
"default": kwargs["default_value"],
"description": kwargs.get("description", ""),
"url_type": "audio"
}
return schema

@classmethod
def VALIDATE_INPUTS(s, audio):
if not folder_paths.exists_annotated_filepath(audio):
return "Invalid audio file: {}".format(audio)
return True

@classmethod
def VALIDATE_INPUTS(s, input_name, default_value, description=""):
audio = default_value
if audio.startswith("http"):
return True

if not folder_paths.exists_annotated_filepath(audio):
return "Invalid audio file: {}".format(audio)
return True

def load(self, input_name, default_value=None, display_name=None, description=None):
input_dir = folder_paths.get_input_directory()
audio_path = default_value
try:
if audio_path.startswith('http'):
import requests
from io import BytesIO
print("Fetching audio from url: ", audio_path)
response = requests.get(audio_path)
response.raise_for_status()
audio_file = BytesIO(response.content)
waveform, sample_rate = torchaudio.load(audio_file)
else:
if not os.path.isfile(audio_path): # abs path
# local path
audio_path = os.path.join(input_dir, audio_path)
waveform, sample_rate = torchaudio.load(audio_path)

audio = {"waveform": waveform.unsqueeze(
0), "sample_rate": sample_rate}
return (audio, )
# image = ImageOps.exif_transpose(image)
# image = image.convert("RGB")
# image = np.array(image).astype(np.float32) / 255.0
# image = torch.from_numpy(image)[None,]
# return [image]
except Exception as e:
raise e


class ShellAgentSaveAudios(SaveAudio):
@classmethod
def INPUT_TYPES(s):
return {"required": {"audio": ("AUDIO", ),
"output_name": ("STRING", {"multiline": False, "default": "output_audio"},),
"filename_prefix": ("STRING", {"default": "audio/ComfyUI"})},
"hidden": {"prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO"},
}
# {
# "required": {
# "images": ("IMAGE", {"tooltip": "The audio to save."}),
# "output_name": ("STRING", {"multiline": False, "default": "output_image"},),
# "filename_prefix": ("STRING", {"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."})
# },
# "hidden": {
# "prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO"
# },
# }

CATEGORY = "shellagent"

@classmethod
def validate(cls, **kwargs):
schema = {
"title": kwargs["output_name"],
"type": "array",
"items": {
"type": "string",
"url_type": "audio",
}
}
return schema

def save_audio(self, audio, filename_prefix="ComfyUI", prompt=None, extra_pnginfo=None, **extra_kwargs):
results = super().save_audio(audio, filename_prefix, prompt, extra_pnginfo)
results["shellagent_kwargs"] = extra_kwargs
return results

def save_flac(self, audio, filename_prefix="ComfyUI", prompt=None, extra_pnginfo=None, **extra_kwargs):
results = super().save_flac(audio, filename_prefix, "flac", prompt, extra_pnginfo)
results["shellagent_kwargs"] = extra_kwargs
return results


class ShellAgentSaveAudio(ShellAgentSaveAudios):
@classmethod
def validate(cls, **kwargs):
schema = {
"title": kwargs["output_name"],
"type": "string",
"url_type": "audio",
}
return schema


NODE_CLASS_MAPPINGS = {
"ShellAgentPluginInputAudio": ShellAgentPluginInputAudio,
"ShellAgentPluginSaveAudios": ShellAgentSaveAudios,
"ShellAgentPluginSaveAudio": ShellAgentSaveAudio,
}

NODE_DISPLAY_NAME_MAPPINGS = {
"ShellAgentPluginInputAudio": "Input Audio (ShellAgent Plugin)",
"ShellAgentPluginSaveAudios": "Save Audios (ShellAgent Plugin)",
"ShellAgentPluginSaveAudio": "Save Audio (ShellAgent Plugin)",
}
108 changes: 92 additions & 16 deletions comfy-nodes/input_image.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,39 @@
import folder_paths
from PIL import Image, ImageOps
import node_helpers

from PIL import Image, ImageOps, ImageSequence, ImageFile
import numpy as np
import torch
import os
import uuid
import tqdm

from io import BytesIO
import PIL
import cv2
from pillow_heif import register_heif_opener

register_heif_opener()

def safe_open_image(image_bytes):
try:
image_pil = Image.open(BytesIO(image_bytes))
except PIL.UnidentifiedImageError as e:
print(e)
# Convert response content (bytes) to a NumPy array
image_array = np.frombuffer(image_bytes, np.uint8)

# Decode the image from the NumPy array (OpenCV format: BGR)
image_cv = cv2.imdecode(image_array, cv2.IMREAD_COLOR)

if image_cv is not None:
# Convert the BGR image to RGB
image_rgb = cv2.cvtColor(image_cv, cv2.COLOR_BGR2RGB)

# Convert the RGB NumPy array to a PIL Image
image_pil = Image.fromarray(image_rgb)
else:
raise ValueError("The image cannot be identified by neither PIL nor OpenCV")
return image_pil

class ShellAgentPluginInputImage:
@classmethod
Expand All @@ -20,20 +48,19 @@ def INPUT_TYPES(s):
{"multiline": False, "default": "input_image", "forceInput": False},
),
"default_value": (
# "STRING", {"image_upload": True, "default": files[0] if len(files) else ""},
sorted(files), {"image_upload": True, "forceInput": False}
),
},
"optional": {
"description": (
"STRING",
{"multiline": True, "default": "", "forceInput": False},
{"multiline": False, "default": "", "forceInput": False},
),
}
}

RETURN_TYPES = ("IMAGE",)
RETURN_NAMES = ("image",)
RETURN_TYPES = ("IMAGE", "MASK")
# RETURN_NAMES = ("image",)

FUNCTION = "run"

Expand All @@ -45,32 +72,80 @@ def validate(cls, **kwargs):
"title": kwargs["input_name"],
"type": "string",
"default": kwargs["default_value"],
"description": kwargs["description"],
"description": kwargs.get("description", ""),
"url_type": "image"
}
return schema

@classmethod
def VALIDATE_INPUTS(s, input_name, default_value, description=""):
image = default_value

if image.startswith("http"):
return True

if image == "":
return "Invalid image file: please check if the image is empty or invalid"

if os.path.isfile(image):
return True

if not folder_paths.exists_annotated_filepath(image):
return "Invalid image file: {}".format(image)

return True

def convert_image_mask(self, img):
output_images = []
output_masks = []
w, h = None, None

excluded_formats = ['MPO']

for i in ImageSequence.Iterator(img):
i = node_helpers.pillow(ImageOps.exif_transpose, i)

if i.mode == 'I':
i = i.point(lambda i: i * (1 / 255))
image = i.convert("RGB")

if len(output_images) == 0:
w = image.size[0]
h = image.size[1]

if image.size[0] != w or image.size[1] != h:
continue

image = np.array(image).astype(np.float32) / 255.0
image = torch.from_numpy(image)[None,]
if 'A' in i.getbands():
mask = np.array(i.getchannel('A')).astype(np.float32) / 255.0
mask = 1. - torch.from_numpy(mask)
else:
mask = torch.zeros((64,64), dtype=torch.float32, device="cpu")
output_images.append(image)
output_masks.append(mask.unsqueeze(0))

if len(output_images) > 1 and img.format not in excluded_formats:
output_image = torch.cat(output_images, dim=0)
output_mask = torch.cat(output_masks, dim=0)
else:
output_image = output_images[0]
output_mask = output_masks[0]

return (output_image, output_mask)


def run(self, input_name, default_value=None, display_name=None, description=None):
input_dir = folder_paths.get_input_directory()
image_path = default_value
input_dir = folder_paths.get_input_directory()
try:
if image_path.startswith('http'):
import requests
from io import BytesIO
print("Fetching image from url: ", image_path)
response = requests.get(image_path)
image = Image.open(BytesIO(response.content))
image = safe_open_image(response.content)
elif image_path.startswith('data:image/png;base64,') or image_path.startswith('data:image/jpeg;base64,') or image_path.startswith('data:image/jpg;base64,'):
import base64
from io import BytesIO
Expand All @@ -82,13 +157,14 @@ def run(self, input_name, default_value=None, display_name=None, description=Non
if not os.path.isfile(image_path): # abs path
# local path
image_path = os.path.join(input_dir, image_path)
image = Image.open(image_path).convert("RGB")

image = ImageOps.exif_transpose(image)
image = image.convert("RGB")
image = np.array(image).astype(np.float32) / 255.0
image = torch.from_numpy(image)[None,]
return [image]
image = node_helpers.pillow(Image.open, image_path)

return self.convert_image_mask(image)
# image = ImageOps.exif_transpose(image)
# image = image.convert("RGB")
# image = np.array(image).astype(np.float32) / 255.0
# image = torch.from_numpy(image)[None,]
# return [image]
except Exception as e:
raise e

Expand Down
Loading