Compare commits

..
29 Commits
Author SHA1 Message Date
shadow c5392aa237 Merge pull request #441 from Creepybits/main
Fix: Correct scheduler input on StyleAligned Sample Reference Latents node
2026-06-04 15:37:37 +08:00
shadow 16a2b55fa1 Merge pull request #462 from LordTaylor/fix/dragdrop-preventdefault-swallows-workflow-drop
Fix: drop handler swallows all drops and breaks ComfyUI native drag&drop
2026-06-04 15:36:43 +08:00
LordTaylorandClaude Opus 4.8 b423b09ff3 Fix: don't swallow non-JSON drops, restoring ComfyUI native drag&drop
The document 'drop' listener called event.preventDefault() and
stopPropagation() unconditionally on every drop. ComfyUI's native
drag&drop handler bails when event.defaultPrevented is already true, so
dropping any workflow file (PNG/JSON/webp) onto the canvas silently did
nothing as long as Mixlab was installed.

Scope preventDefault()/stopPropagation() to the application/json case
actually handled here, so all other drops fall through to ComfyUI.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-30 23:25:58 +02:00
Creepybits e66add88cb Update Style.py
Fix: 
Correct scheduler input on StyleAligned Sample Reference Latents node

Description:
The scheduler input for the StyleAligned Sample Reference Latents node was defined using a .reverse() method, which caused it to register as an invalid input type that could not accept connections.

This commit changes the input from a broken socket to a dropdown widget, making it consistent with the StyleAligned Reference Sampler node and allowing it to function as intended.
2025-10-03 00:26:06 +02:00
shadow 32b22c39cb Merge pull request #411 from ComfyNodePRs/update-publish-yaml
Update Github Action for Publishing to Comfy Registry
2025-07-22 09:44:28 +08:00
shadow 259baac177 Merge pull request #430 from torzdf/main
Bugfix: Give EditMask temp files unique filenames
2025-07-22 09:44:03 +08:00
torzdf 67ef8c13a8 Bugfix: Give EditMask temp files unique filenames 2025-07-01 00:50:43 +01:00
shadow b2bb1876de Merge pull request #393 from wengxiaoxiong/main
fix: Pillow 10.0.0+ compatibility
2025-02-05 18:24:45 +08:00
wengxiaoxiong cda4e626e7 fix: fix 'FreeTypeFont' object has no attribute 'getsize' 2025-02-01 15:33:24 +08:00
shadow d835aff0cb Merge pull request #391 from TangYanxin/main
Solve the problem that there are two duplicate badges on the node
2025-01-25 10:35:15 +08:00
snomiao 21e1967c5e chore(publish): update GitHub Actions workflow for node publishing
- Add permissions for writing issues
- Update action version to v1 for publish-node-action
- Add condition to run job only for specific repository owner
2025-01-20 21:29:05 +00:00
唐焱鑫 c9b5baf4d9 Update ui_mixlab.js: if ComfyUI already comes with a badge, don't add a new badge. 2025-01-15 01:16:48 +08:00
shadow 67c974c96e Merge pull request #374 from hieuck/fix-module-'PIL.Image'-has-no-attribute-'ANTIALIAS'
- **Exception Message:** module 'PIL.Image' has no attribute 'ANTIALIAS'
2024-11-26 19:59:58 +08:00
Lê Trung Hiếu b46ccb03c9 Update ImageNode.py 2024-11-26 07:10:48 +07:00
shadow 0ecf98e08b Merge pull request #361 from shiertier/main
fix GFW block cdn.jsdelivr.net
2024-11-21 09:40:33 +08:00
shiertier f024034724 do not perform get object in init 2024-11-01 12:48:48 +08:00
shiertier 327a21f009 Merge pull request #1 from dionren/main
fix GFW block cdn.jsdelivr.net
2024-11-01 00:47:50 +08:00
任嘉 cfc51532b8 fix GFW block wavesurfer.esm.js 2024-10-27 22:03:48 +08:00
任嘉 00988f92e4 fix GFW block cdn.jsdelivr.net
Add wavesurfer.esm.js
2024-10-27 22:02:13 +08:00
shadowcz007 868c6085a8 Update extension-node-map.json 2024-10-25 14:24:39 +08:00
shadowcz007 a47a56bda0 Update ImageNode.py 2024-10-21 08:31:05 +08:00
shadowcz007 3667b42b2f Update ImageNode.py 2024-10-21 08:28:11 +08:00
shadowcz007 7d142d7d62 Update extension-node-map.json 2024-10-19 11:53:10 +08:00
shadow 24863e2ed3 Merge pull request #350 from shadowcz007/video-all-in-one-fal
0.46.0
2024-10-14 10:44:52 +08:00
shadowcz007 fe8b526bbb 0.46.0 2024-10-14 10:44:05 +08:00
shadowcz007 6298be393a add workflow# 2024-10-14 09:46:03 +08:00
shadowcz007 3a7853f9cc init 2024-10-14 09:19:55 +08:00
shadowcz007 4a9413c83d Update ChatGPT.py 2024-10-12 20:54:20 +08:00
shadowcz007 21b04d62ae Update README.md 2024-10-12 20:42:28 +08:00
16 changed files with 2056 additions and 156 deletions
+6 -2
View File
@@ -7,15 +7,19 @@ on:
paths:
- "pyproject.toml"
permissions:
issues: write
jobs:
publish-node:
name: Publish Custom Node to registry
runs-on: ubuntu-latest
if: ${{ github.repository_owner == 'shadowcz007' }}
steps:
- name: Check out code
uses: actions/checkout@v4
- name: Publish Custom Node
uses: Comfy-Org/publish-node-action@main
uses: Comfy-Org/publish-node-action@v1
with:
## Add your own personal access token to your Github Repository secrets and reference it here.
personal_access_token: ${{ secrets.REGISTRY_ACCESS_TOKEN }}
personal_access_token: ${{ secrets.REGISTRY_ACCESS_TOKEN }}
+3 -1
View File
@@ -10,7 +10,9 @@ For business cooperation, please contact email 389570357@qq.com
##### `最新`:
- 新增 SimulateDevDesignDiscussions,需要安装[swarm](https://github.com/openai/swarm)和[Comfyui-ChatTTS](https://github.com/shadowcz007/Comfyui-ChatTTS),工作流下载[./workflow/swarm制作的播客节点workflow.json]
- 新增[fal.ai](https://fal.ai/dashboard)的视频生成:Kling、RunwayGen3、LumaDreamMachine,[工作流下载](./workflow/video-all-in-one-test-workflow.json)
- 新增 SimulateDevDesignDiscussions,需要安装[swarm](https://github.com/openai/swarm)和[Comfyui-ChatTTS](https://github.com/shadowcz007/Comfyui-ChatTTS),[工作流下载](./workflow/swarm制作的播客节点workflow.json)
- 新增 SenseVoice
+19
View File
@@ -1455,4 +1455,23 @@ try:
except Exception as e:
logging.info('Whisper.available False' )
try:
from .nodes.FalVideo import VideoGenKlingNode,VideoGenLumaDreamMachineNode,VideoGenRunwayGen3Node,LoadVideoFromURL
logging.info('FalVideo.available')
# Update Node class mappings
NODE_CLASS_MAPPINGS['VideoGenKlingNode']=VideoGenKlingNode
NODE_CLASS_MAPPINGS['VideoGenRunwayGen3Node']=VideoGenRunwayGen3Node
NODE_CLASS_MAPPINGS['VideoGenLumaDreamMachineNode']=VideoGenLumaDreamMachineNode
NODE_CLASS_MAPPINGS['LoadVideoFromURL']=LoadVideoFromURL
NODE_DISPLAY_NAME_MAPPINGS["VideoGenKlingNode"]= "Kling Video Generation @fal"
NODE_DISPLAY_NAME_MAPPINGS["VideoGenRunwayGen3Node"]= "Runway Gen3 Image-to-Video @fal"
NODE_DISPLAY_NAME_MAPPINGS["VideoGenLumaDreamMachineNode"]= "Luma Dream Machine @fal"
NODE_DISPLAY_NAME_MAPPINGS["LoadVideoFromURL"]= "Load Video from URL"
except Exception as e:
logging.info('FalVideo.available False' )
logging.info('\033[93m -------------- \033[0m')
File diff suppressed because it is too large Load Diff
+1 -6
View File
@@ -843,12 +843,7 @@ class JsonRepair:
# 以下为固定提示词的LLM节点示例
class SimulateDevDesignDiscussions:
def __init__(self):
# self.__client = OpenAI()
self.session_history = [] # 用于存储会话历史的列表
# self.seed=0
self.system_content="You are ChatGPT, a large language model trained by OpenAI. Answer as concisely as possible."
@classmethod
def INPUT_TYPES(cls):
+332
View File
@@ -0,0 +1,332 @@
# 修改自 https://github.com/gokayfem/ComfyUI-fal-API/blob/main/nodes/video_node.py
# image-to-video all in one
import os,sys
import torch
from PIL import Image
import tempfile
import numpy as np
import requests
import cv2
import subprocess
import importlib.util
python = sys.executable
def is_installed(package, package_overwrite=None,auto_install=True):
is_has=False
try:
spec = importlib.util.find_spec(package)
is_has=spec is not None
except ModuleNotFoundError:
pass
package = package_overwrite or package
if spec is None:
if auto_install==True:
print(f"Installing {package}...")
# 清华源 -i https://pypi.tuna.tsinghua.edu.cn/simple
command = f'"{python}" -m pip install {package}'
result = subprocess.run(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True, env=os.environ)
is_has=True
if result.returncode != 0:
print(f"Couldn't install\nCommand: {command}\nError code: {result.returncode}")
is_has=False
else:
print(package+'## OK')
return is_has
try:
if is_installed('fal_client','fal-client')==True:
from fal_client import submit, upload_file
except:
print("#install fal-client error")
def upload_image(image):
try:
# Convert the image tensor to a numpy array
if isinstance(image, torch.Tensor):
image_np = image.cpu().numpy()
else:
image_np = np.array(image)
# Ensure the image is in the correct format (H, W, C)
if image_np.ndim == 4:
image_np = image_np.squeeze(0) # Remove batch dimension if present
if image_np.ndim == 2:
image_np = np.stack([image_np] * 3, axis=-1) # Convert grayscale to RGB
elif image_np.shape[0] == 3:
image_np = np.transpose(image_np, (1, 2, 0)) # Change from (C, H, W) to (H, W, C)
# Normalize the image data to 0-255 range
if image_np.dtype == np.float32 or image_np.dtype == np.float64:
image_np = (image_np * 255).astype(np.uint8)
# Convert to PIL Image
pil_image = Image.fromarray(image_np)
# Save the image to a temporary file
with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as temp_file:
pil_image.save(temp_file, format="PNG")
temp_file_path = temp_file.name
# Upload the temporary file
image_url = upload_file(temp_file_path)
return image_url
except Exception as e:
print(f"Error uploading image: {str(e)}")
return None
finally:
# Clean up the temporary file
if 'temp_file_path' in locals():
os.unlink(temp_file_path)
class VideoGenKlingNode:
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"prompt": ("STRING", {"default": "", "multiline": True}),
"duration": (["5", "10"], {"default": "5"}),
"aspect_ratio": (["16:9", "9:16", "1:1"], {"default": "16:9"}),
"mode": (["standard", "pro"], {"default": "standard"}),
"fal_key":("STRING", {"forceInput": True,}),
},
"optional": {
"image": ("IMAGE",),
},
}
RETURN_TYPES = ("STRING",)
FUNCTION = "generate_video"
CATEGORY = "♾️Mixlab/Video"
def generate_video(self, prompt, duration, aspect_ratio,mode,fal_key, image=None):
arguments = {
"prompt": prompt,
"duration": duration,
"aspect_ratio": aspect_ratio,
}
os.environ["FAL_KEY"] = fal_key
api_url="fal-ai/kling-video/v1/"+mode
try:
if image is not None:
image_url = upload_image(image)
if image_url:
arguments["image_url"] = image_url
handler = submit(api_url+"/image-to-video", arguments=arguments)
else:
return ("Error: Unable to upload image.",)
else:
handler = submit(api_url+"/text-to-video", arguments=arguments)
result = handler.get()
video_url = result["video"]["url"]
return (video_url,)
except Exception as e:
print(f"Error generating video: {str(e)}")
return ("Error: Unable to generate video.",)
class VideoGenRunwayGen3Node:
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"prompt": ("STRING", {"default": "", "multiline": True}),
"image": ("IMAGE",),
"duration": (["5", "10"], {"default": "5"}),
"aspect_ratio": (["16:9", "9:16"], {"default": "16:9"}),
"fal_key":("STRING", {"forceInput": True,}),
},
}
RETURN_TYPES = ("STRING",)
FUNCTION = "generate_video"
CATEGORY = "♾️Mixlab/Video"
def generate_video(self, prompt, image, duration,aspect_ratio,fal_key):
os.environ["FAL_KEY"] = fal_key
try:
image_url = upload_image(image)
if not image_url:
return ("Error: Unable to upload image.",)
arguments = {
"prompt": prompt,
"image_url": image_url,
"duration": duration,
"ratio":aspect_ratio
}
handler = submit("fal-ai/runway-gen3/turbo/image-to-video", arguments=arguments)
result = handler.get()
video_url = result["video"]["url"]
return (video_url,)
except Exception as e:
print(f"Error generating video: {str(e)}")
return ("Error: Unable to generate video.",)
class VideoGenLumaDreamMachineNode:
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"prompt": ("STRING", {"default": "", "multiline": True}),
"aspect_ratio": (["16:9", "9:16", "4:3", "3:4", "21:9", "9:21"], {"default": "16:9"}),
"fal_key":("STRING", {"forceInput": True,}),
},
"optional": {
"image": ("IMAGE",),
"loop": ("BOOLEAN", {"default": True}),
},
}
RETURN_TYPES = ("STRING",)
FUNCTION = "generate_video"
CATEGORY = "♾️Mixlab/Video"
def generate_video(self, prompt, aspect_ratio,fal_key, image=None, loop=True):
os.environ["FAL_KEY"] = fal_key
arguments = {
"prompt": prompt,
"aspect_ratio": aspect_ratio,
"loop": loop,
}
try:
if image is not None:
image_url = upload_image(image)
if not image_url:
return ("Error: Unable to upload image.",)
arguments["image_url"] = image_url
endpoint = "fal-ai/luma-dream-machine/image-to-video"
else:
endpoint = "fal-ai/luma-dream-machine"
handler = submit(endpoint, arguments=arguments)
result = handler.get()
video_url = result["video"]["url"]
return (video_url,)
except Exception as e:
print(f"Error generating video: {str(e)}")
return ("Error: Unable to generate video.",)
class LoadVideoFromURL:
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"url": ("STRING", {"default": "https://example.com/video.mp4"}),
"force_rate": ("INT", {"default": 0, "min": 0, "max": 60, "step": 1}),
"force_size": (["Disabled", "Custom Height", "Custom Width", "Custom", "256x?", "?x256", "256x256", "512x?", "?x512", "512x512"],),
"custom_width": ("INT", {"default": 512, "min": 0, "max": 8192, "step": 8}),
"custom_height": ("INT", {"default": 512, "min": 0, "max": 8192, "step": 8}),
"frame_load_cap": ("INT", {"default": 0, "min": 0, "max": 1000000, "step": 1}),
"skip_first_frames": ("INT", {"default": 0, "min": 0, "max": 1000000, "step": 1}),
"select_every_nth": ("INT", {"default": 1, "min": 1, "max": 1000000, "step": 1}),
},
}
RETURN_TYPES = ("IMAGE", "INT", "VHS_VIDEOINFO")
RETURN_NAMES = ("frames", "frame_count", "video_info")
FUNCTION = "load_video_from_url"
CATEGORY = "♾️Mixlab/Video"
def load_video_from_url(self, url, force_rate, force_size, custom_width, custom_height, frame_load_cap, skip_first_frames, select_every_nth):
# Download the video to a temporary file
with tempfile.NamedTemporaryFile(delete=False, suffix=".mp4") as temp_file:
response = requests.get(url, stream=True)
for chunk in response.iter_content(chunk_size=8192):
temp_file.write(chunk)
temp_file_path = temp_file.name
# Load the video using OpenCV
cap = cv2.VideoCapture(temp_file_path)
# Get video properties
fps = cap.get(cv2.CAP_PROP_FPS)
total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
duration = total_frames / fps
# Calculate target size
if force_size != "Disabled":
if force_size == "Custom Width":
new_height = int(height * (custom_width / width))
new_width = custom_width
elif force_size == "Custom Height":
new_width = int(width * (custom_height / height))
new_height = custom_height
elif force_size == "Custom":
new_width, new_height = custom_width, custom_height
else:
target_width, target_height = map(int, force_size.replace("?", "0").split("x"))
if target_width == 0:
new_width = int(width * (target_height / height))
new_height = target_height
else:
new_height = int(height * (target_width / width))
new_width = target_width
else:
new_width, new_height = width, height
frames = []
frame_count = 0
for i in range(total_frames):
ret, frame = cap.read()
if not ret:
break
if i < skip_first_frames:
continue
if (i - skip_first_frames) % select_every_nth != 0:
continue
if force_size != "Disabled":
frame = cv2.resize(frame, (new_width, new_height))
frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
frame = torch.from_numpy(frame).float() / 255.0
frames.append(frame)
frame_count += 1
if frame_load_cap > 0 and frame_count >= frame_load_cap:
break
cap.release()
os.unlink(temp_file_path)
frames = torch.stack(frames)
video_info = {
"source_fps": fps,
"source_frame_count": total_frames,
"source_duration": duration,
"source_width": width,
"source_height": height,
"loaded_fps": fps if force_rate == 0 else force_rate,
"loaded_frame_count": frame_count,
"loaded_duration": frame_count / (fps if force_rate == 0 else force_rate),
"loaded_width": new_width,
"loaded_height": new_height,
}
return (frames, frame_count, video_info)
+22 -10
View File
@@ -123,8 +123,13 @@ def composite_images(foreground, background, mask, is_multiply_blend=False, posi
}
# Resize the foreground image with antialiasing
layer_image = layer['image'].resize((layer['width'], layer['height']), Image.ANTIALIAS)
layer_mask = layer['mask'].resize((layer['width'], layer['height']), Image.ANTIALIAS)
try:
resampling_method = Image.Resampling.LANCZOS
except AttributeError:
resampling_method = Image.ANTIALIAS
layer_image = layer['image'].resize((layer['width'], layer['height']), resampling_method)
layer_mask = layer['mask'].resize((layer['width'], layer['height']), resampling_method)
bg_image.paste(layer_image, (layer['x'], layer['y']), layer_mask)
@@ -1041,29 +1046,33 @@ def generate_text_image(text,
if layout == "vertical":
for line in lines:
max_char_width = max(font.getsize(char)[0] for char in line)
max_char_width = max(font.getbbox(char)[2] - font.getbbox(char)[0] for char in line)
for char in line:
char_width, char_height = font.getsize(char)
left, top, right, bottom = font.getbbox(char)
char_width = right - left
char_height = bottom - top
char_coordinates.append((x, y))
y += char_height + spacing
max_height = max(max_height, y + padding)
x += max_char_width + line_spacing
y = padding
max_width = x
total_line_width = sum(font.getsize(line)[1] for line in lines)
total_line_width = sum(font.getbbox(line)[2] - font.getbbox(line)[0] for line in lines)
total_spacing = line_spacing * (len(lines) - 1)
max_width = total_line_width + total_spacing + padding * 2
else:
for line in lines:
line_width, line_height = font.getsize(line)
line_width, line_height = font.getbbox(line)[2] - font.getbbox(line)[0], font.getbbox(line)[3] - font.getbbox(line)[1]
for char in line:
char_width, char_height = font.getsize(char)
left, top, right, bottom = font.getbbox(char)
char_width = right - left
char_height = bottom - top
char_coordinates.append((x, y))
x += char_width + spacing
max_width = max(max_width, x + padding)
y += line_height + line_spacing
x = padding
total_line_heights = sum(font.getsize(line)[1] for line in lines)
total_line_heights = sum(font.getbbox(line)[3] - font.getbbox(line)[1] for line in lines)
total_spacing = line_spacing * (len(lines) - 1)
max_height = total_line_heights + total_spacing + padding * 2
@@ -2951,11 +2960,14 @@ class ResizeImage:
im=tensor2pil(im)
im=im.convert('RGB')
a_im,hex=get_average_color_image(im)
a_im,hex=get_average_color_image(im)
if average_color=='on':
fill_color=hex
a_im=resize_image(a_im,scale_option,w,h,fill_color)
im=resize_image(im,scale_option,w,h,fill_color)
im=pil2tensor(im)
+1 -1
View File
@@ -234,7 +234,7 @@ class StyleAlignedSampleReferenceLatents:
"steps": ("INT", {"default": 20, "min": 1, "max": 10000}),
"cfg": ("FLOAT", {"default": 8.0, "min": 0.0, "max": 100.0, "step":0.1, "round": 0.01}),
"scheduler": (comfy.samplers.KSampler.SCHEDULERS.reverse(), ),
"scheduler": (comfy.samplers.KSampler.SCHEDULERS, ),
"denoise": ("FLOAT", {"default": 1, "min": 0.0, "max": 1.0, "step": 0.01}),
}
+6 -4
View File
@@ -7,6 +7,7 @@ import os
import folder_paths
import node_helpers
import hashlib
from uuid import uuid4
# Tensor to PIL
def tensor2pil(image):
@@ -26,7 +27,7 @@ def tensor_to_hash(tensor):
return hash_value
def create_temp_file(image):
def create_temp_file(image, uuid):
output_dir = folder_paths.get_temp_directory()
(
@@ -35,7 +36,7 @@ def create_temp_file(image):
counter,
subfolder,
_,
) = folder_paths.get_save_image_path('material', output_dir)
) = folder_paths.get_save_image_path(f'material_{uuid}', output_dir)
image=tensor2pil(image)
@@ -59,6 +60,7 @@ class EditMask:
def __init__(self):
self.image_id = None
self.uuid = str(uuid4())
@classmethod
def INPUT_TYPES(s):
@@ -117,13 +119,13 @@ class EditMask:
image_path = os.path.join(base_dir,subfolder, name)
if image_path==None:
image_path,images=create_temp_file(image)
image_path,images=create_temp_file(image, self.uuid)
print('#image_path',os.path.exists(image_path),image_path)
# image_path = folder_paths.get_annotated_filepath(image) #文件名
if not os.path.exists(image_path):
image_path,images=create_temp_file(image)
image_path,images=create_temp_file(image, self.uuid)
img = node_helpers.pillow(Image.open, image_path)
+1 -1
View File
@@ -1,7 +1,7 @@
[project]
name = "comfyui-mixlab-nodes"
description = "3D, ScreenShareNode & FloatingVideoNode, SpeechRecognition & SpeechSynthesis, GPT, LoadImagesFromLocal, Layers, Other Nodes, ..."
version = "0.45.0"
version = "0.46.0"
license = "MIT"
dependencies = ["numpy", "pyOpenSSL", "watchdog", "opencv-python-headless", "matplotlib", "openai", "simple-lama-inpainting", "clip-interrogator==0.6.0", "transformers>=4.36.0", "lark-parser", "imageio-ffmpeg", "rembg[gpu]", "omegaconf==2.3.0", "Pillow>=9.5.0", "einops==0.7.0", "trimesh>=4.0.5", "huggingface-hub", "scikit-image"]
+10 -4
View File
@@ -267,7 +267,13 @@ function downloadJsonFile (jsonData, fileName = 'mix_app.json') {
}
async function save (json, download = false, showInfo = true) {
let nodesAll = window._nodesAll || (await getObjectInfo())
if (!window._nodesAll) {
window._nodesAll = await getObjectInfo();
}
let nodesAll = window._nodesAll;
// let nodesAll = window._nodesAll || (await getObjectInfo())
console.log('####SAVE', nodesAll, json)
@@ -417,9 +423,9 @@ function getInputsAndOutputs () {
app.registerExtension({
name: 'Mixlab.utils.AppInfo',
init () {
if (!window._nodesAll) {
getObjectInfo().then(r => (window._nodesAll = r))
}
// if (!window._nodesAll) {
// getObjectInfo().then(r => (window._nodesAll = r))
// }
},
async beforeRegisterNodeDef (nodeType, nodeData, app) {
if (nodeType.comfyClass == 'AppInfo') {
+1 -1
View File
@@ -3,7 +3,7 @@ import { app } from '../../../scripts/app.js'
const repoOwner = 'shadowcz007' // 替换为仓库的所有者
const repoName = 'comfyui-mixlab-nodes' // 替换为仓库的名称
const version = 'v0.45.0'
const version = 'v0.46.0'
fetch(`https://api.github.com/repos/${repoOwner}/${repoName}/releases/latest`)
.then(response => response.json())
+1 -1
View File
@@ -3,7 +3,7 @@ import { api } from '../../../scripts/api.js'
import { ComfyWidgets } from '../../../scripts/widgets.js'
import { $el } from '../../../scripts/ui.js'
import WaveSurfer from 'https://cdn.jsdelivr.net/npm/wavesurfer.js@7/dist/wavesurfer.esm.js'
import WaveSurfer from './wavesurfer.esm.js'
function get_position_style (ctx, widget_width, y, node_height) {
const MARGIN = 4 // the margin around the html element
+10 -4
View File
@@ -1735,14 +1735,16 @@ app.registerExtension({
// 把json往里 拖
document.addEventListener('drop', async event => {
event.preventDefault()
event.stopPropagation()
// Dragging from Chrome->Firefox there is a file but its a bmp, so ignore that
// Only intercept the JSON files handled here. Calling preventDefault()
// unconditionally swallowed every drop, so ComfyUI's native drag&drop
// (guarded by event.defaultPrevented) never loaded dropped workflows
// (PNG / JSON / etc.). Keep preventDefault scoped to the handled case.
if (
event.dataTransfer.files.length &&
event.dataTransfer.files[0].type == 'application/json'
) {
event.preventDefault()
event.stopPropagation()
const reader = new FileReader()
reader.onload = async () => {
loadAppJson(reader.result)
@@ -2171,6 +2173,10 @@ app.registerExtension({
fetch('manager/badge_mode').then(r => {
if (r.status === 404) {
// 已有ComfyUI自带的badge
if(node.badges?.[0]?.()){
return
}
// 右上角的badge是否已经绘制
if (!node.badge_enabled) {
if (!node.getNickname) {
File diff suppressed because one or more lines are too long
@@ -0,0 +1,775 @@
{
"last_node_id": 18,
"last_link_id": 15,
"nodes": [
{
"id": 6,
"type": "LoadImage",
"pos": [
-32,
79
],
"size": {
"0": 315,
"1": 314
},
"flags": {},
"order": 0,
"mode": 0,
"outputs": [
{
"name": "IMAGE",
"type": "IMAGE",
"links": [
1,
2,
3
],
"shape": 3,
"slot_index": 0
},
{
"name": "MASK",
"type": "MASK",
"links": null,
"shape": 3
}
],
"properties": {
"Node name for S&R": "LoadImage"
},
"widgets_values": [
"1.jpg",
"image"
]
},
{
"id": 7,
"type": "KeyInput",
"pos": [
-34,
585
],
"size": {
"0": 315,
"1": 94
},
"flags": {},
"order": 1,
"mode": 0,
"outputs": [
{
"name": "key",
"type": "STRING",
"links": [
4,
5,
6
],
"shape": 3,
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "KeyInput"
},
"widgets_values": [
null,
null
]
},
{
"id": 10,
"type": "LoadVideoFromURL",
"pos": [
1038,
21
],
"size": [
315,
266
],
"flags": {},
"order": 5,
"mode": 0,
"inputs": [
{
"name": "url",
"type": "STRING",
"link": 7,
"widget": {
"name": "url"
}
}
],
"outputs": [
{
"name": "frames",
"type": "IMAGE",
"links": [
8
],
"shape": 3,
"slot_index": 0
},
{
"name": "frame_count",
"type": "INT",
"links": null,
"shape": 3
},
{
"name": "video_info",
"type": "VHS_VIDEOINFO",
"links": null,
"shape": 3
}
],
"properties": {
"Node name for S&R": "LoadVideoFromURL"
},
"widgets_values": [
"https://example.com/video.mp4",
0,
"Disabled",
512,
512,
0,
0,
1
]
},
{
"id": 13,
"type": "LoadVideoFromURL",
"pos": [
1032,
715
],
"size": {
"0": 315,
"1": 266
},
"flags": {},
"order": 9,
"mode": 0,
"inputs": [
{
"name": "url",
"type": "STRING",
"link": 10,
"widget": {
"name": "url"
}
}
],
"outputs": [
{
"name": "frames",
"type": "IMAGE",
"links": [
12
],
"shape": 3,
"slot_index": 0
},
{
"name": "frame_count",
"type": "INT",
"links": null,
"shape": 3
},
{
"name": "video_info",
"type": "VHS_VIDEOINFO",
"links": null,
"shape": 3
}
],
"properties": {
"Node name for S&R": "LoadVideoFromURL"
},
"widgets_values": [
"https://example.com/video.mp4",
0,
"Disabled",
512,
512,
0,
0,
1
]
},
{
"id": 11,
"type": "PreviewImage",
"pos": [
1436,
6
],
"size": [
210,
246
],
"flags": {},
"order": 11,
"mode": 0,
"inputs": [
{
"name": "images",
"type": "IMAGE",
"link": 8
}
],
"properties": {
"Node name for S&R": "PreviewImage"
}
},
{
"id": 3,
"type": "VideoGenKlingNode",
"pos": [
527.4816383216086,
19.100152539671797
],
"size": {
"0": 400,
"1": 200
},
"flags": {},
"order": 2,
"mode": 0,
"inputs": [
{
"name": "image",
"type": "IMAGE",
"link": 1
},
{
"name": "fal_key",
"type": "STRING",
"link": 4,
"widget": {
"name": "fal_key"
}
}
],
"outputs": [
{
"name": "STRING",
"type": "STRING",
"links": [
7,
13
],
"shape": 3,
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "VideoGenKlingNode"
},
"widgets_values": [
"The man is shaking his head with a wry smile.\n\n",
"5",
"16:9",
"standard",
""
]
},
{
"id": 16,
"type": "ShowTextForGPT",
"pos": [
1702,
-85
],
"size": [
400,
200
],
"flags": {},
"order": 6,
"mode": 0,
"inputs": [
{
"name": "text",
"type": "STRING",
"link": 13,
"widget": {
"name": "text"
}
},
{
"name": "output_dir",
"type": "STRING",
"link": null,
"widget": {
"name": "output_dir"
}
}
],
"outputs": [
{
"name": "STRING",
"type": "STRING",
"links": null,
"shape": 6
}
],
"properties": {
"Node name for S&R": "ShowTextForGPT"
},
"widgets_values": [
"",
"",
"https://v2.fal.media/files/0f9f44093c7442d5b0819616880bca65_output.mp4"
]
},
{
"id": 4,
"type": "VideoGenRunwayGen3Node",
"pos": [
529,
297
],
"size": {
"0": 400,
"1": 200
},
"flags": {},
"order": 3,
"mode": 0,
"inputs": [
{
"name": "image",
"type": "IMAGE",
"link": 2
},
{
"name": "fal_key",
"type": "STRING",
"link": 5,
"widget": {
"name": "fal_key"
}
}
],
"outputs": [
{
"name": "STRING",
"type": "STRING",
"links": [
9,
14
],
"shape": 3,
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "VideoGenRunwayGen3Node"
},
"widgets_values": [
"The man is shaking his head with a wry smile.\n\n",
"5",
"16:9",
""
]
},
{
"id": 5,
"type": "VideoGenLumaDreamMachineNode",
"pos": [
534,
570
],
"size": {
"0": 400,
"1": 200
},
"flags": {},
"order": 4,
"mode": 0,
"inputs": [
{
"name": "image",
"type": "IMAGE",
"link": 3
},
{
"name": "fal_key",
"type": "STRING",
"link": 6,
"widget": {
"name": "fal_key"
}
}
],
"outputs": [
{
"name": "STRING",
"type": "STRING",
"links": [
10,
15
],
"shape": 3,
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "VideoGenLumaDreamMachineNode"
},
"widgets_values": [
"The man is shaking his head with a wry smile.\n\n",
"16:9",
"",
true
]
},
{
"id": 18,
"type": "ShowTextForGPT",
"pos": [
1719,
665
],
"size": [
400,
200
],
"flags": {},
"order": 10,
"mode": 0,
"inputs": [
{
"name": "text",
"type": "STRING",
"link": 15,
"widget": {
"name": "text"
}
},
{
"name": "output_dir",
"type": "STRING",
"link": null,
"widget": {
"name": "output_dir"
}
}
],
"outputs": [
{
"name": "STRING",
"type": "STRING",
"links": null,
"shape": 6
}
],
"properties": {
"Node name for S&R": "ShowTextForGPT"
},
"widgets_values": [
"",
"",
"https://v2.fal.media/files/a622a5aac002452ba0e75f7d8871389d_output.mp4"
]
},
{
"id": 12,
"type": "LoadVideoFromURL",
"pos": [
1049,
349
],
"size": {
"0": 315,
"1": 266
},
"flags": {},
"order": 7,
"mode": 0,
"inputs": [
{
"name": "url",
"type": "STRING",
"link": 9,
"widget": {
"name": "url"
}
}
],
"outputs": [
{
"name": "frames",
"type": "IMAGE",
"links": [
11
],
"shape": 3,
"slot_index": 0
},
{
"name": "frame_count",
"type": "INT",
"links": null,
"shape": 3
},
{
"name": "video_info",
"type": "VHS_VIDEOINFO",
"links": null,
"shape": 3
}
],
"properties": {
"Node name for S&R": "LoadVideoFromURL"
},
"widgets_values": [
"https://example.com/video.mp4",
0,
"Disabled",
512,
512,
0,
0,
1
]
},
{
"id": 17,
"type": "ShowTextForGPT",
"pos": [
1711,
318
],
"size": [
400,
200
],
"flags": {},
"order": 8,
"mode": 0,
"inputs": [
{
"name": "text",
"type": "STRING",
"link": 14,
"widget": {
"name": "text"
}
},
{
"name": "output_dir",
"type": "STRING",
"link": null,
"widget": {
"name": "output_dir"
}
}
],
"outputs": [
{
"name": "STRING",
"type": "STRING",
"links": null,
"shape": 6
}
],
"properties": {
"Node name for S&R": "ShowTextForGPT"
},
"widgets_values": [
"",
"",
"https://v2.fal.media/files/755d51c8984a4445a852268a209b07e4_output.mp4"
]
},
{
"id": 15,
"type": "PreviewImage",
"pos": [
1444,
650
],
"size": [
210,
246
],
"flags": {},
"order": 13,
"mode": 0,
"inputs": [
{
"name": "images",
"type": "IMAGE",
"link": 12
}
],
"properties": {
"Node name for S&R": "PreviewImage"
}
},
{
"id": 14,
"type": "PreviewImage",
"pos": [
1416,
310
],
"size": [
210,
246
],
"flags": {},
"order": 12,
"mode": 0,
"inputs": [
{
"name": "images",
"type": "IMAGE",
"link": 11
}
],
"properties": {
"Node name for S&R": "PreviewImage"
}
}
],
"links": [
[
1,
6,
0,
3,
0,
"IMAGE"
],
[
2,
6,
0,
4,
0,
"IMAGE"
],
[
3,
6,
0,
5,
0,
"IMAGE"
],
[
4,
7,
0,
3,
1,
"STRING"
],
[
5,
7,
0,
4,
1,
"STRING"
],
[
6,
7,
0,
5,
1,
"STRING"
],
[
7,
3,
0,
10,
0,
"STRING"
],
[
8,
10,
0,
11,
0,
"IMAGE"
],
[
9,
4,
0,
12,
0,
"STRING"
],
[
10,
5,
0,
13,
0,
"STRING"
],
[
11,
12,
0,
14,
0,
"IMAGE"
],
[
12,
13,
0,
15,
0,
"IMAGE"
],
[
13,
3,
0,
16,
0,
"STRING"
],
[
14,
4,
0,
17,
0,
"STRING"
],
[
15,
5,
0,
18,
0,
"STRING"
]
],
"groups": [],
"config": {},
"extra": {
"ds": {
"scale": 0.7247295000000012,
"offset": [
-59.526177135875514,
197.10624904779425
]
}
},
"version": 0.4
}