Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
7 changes: 4 additions & 3 deletions comfy_extras/nodes_audio.py
Original file line number Diff line number Diff line change
Expand Up @@ -543,7 +543,7 @@ def define_schema(cls):
return IO.Schema(
node_id="AudioConcat",
search_aliases=["join audio", "combine audio", "append audio"],
display_name="Audio Concat",
display_name="Concatenate Audio",
description="Concatenates the audio1 to audio2 in the specified direction.",
category="audio",
inputs=[
Expand Down Expand Up @@ -597,7 +597,7 @@ def define_schema(cls):
return IO.Schema(
node_id="AudioMerge",
search_aliases=["mix audio", "overlay audio", "layer audio"],
display_name="Audio Merge",
display_name="Merge Audio",
description="Combine two audio tracks by overlaying their waveforms.",
category="audio",
inputs=[
Expand Down Expand Up @@ -667,8 +667,9 @@ def define_schema(cls):
return IO.Schema(
node_id="AudioAdjustVolume",
search_aliases=["audio gain", "loudness", "audio level"],
display_name="Audio Adjust Volume",
display_name="Adjust Audio Volume",
category="audio",
description="Adjust the volume of the audio by a specified amount in decibels (dB).",
inputs=[
IO.Audio.Input("audio"),
IO.Int.Input(
Expand Down
188 changes: 129 additions & 59 deletions comfy_extras/nodes_dataset.py

Large diffs are not rendered by default.

9 changes: 6 additions & 3 deletions comfy_extras/nodes_hunyuan3d.py
Original file line number Diff line number Diff line change
Expand Up @@ -419,15 +419,17 @@ class VoxelToMeshBasic(IO.ComfyNode):
def define_schema(cls):
return IO.Schema(
node_id="VoxelToMeshBasic",
display_name="Voxel to Mesh (Basic)",
display_name="Voxel to Mesh (Basic) (DEPRECATED)",
category="3d",
description="Converts a voxel grid to a mesh.",
is_deprecated=True, # This node is superseded by the Voxel To Mesh node
inputs=[
IO.Voxel.Input("voxel"),
IO.Float.Input("threshold", default=0.6, min=-1.0, max=1.0, step=0.01),
],
outputs=[
IO.Mesh.Output(),
]
],
)

@classmethod
Expand All @@ -453,9 +455,10 @@ def define_schema(cls):
node_id="VoxelToMesh",
display_name="Voxel to Mesh",
category="3d",
description="Converts a voxel grid to a mesh.",
inputs=[
IO.Voxel.Input("voxel"),
IO.Combo.Input("algorithm", options=["surface net", "basic"], advanced=True),
IO.Combo.Input("algorithm", options=["surface net", "basic"]),
IO.Float.Input("threshold", default=0.6, min=-1.0, max=1.0, step=0.01),
],
outputs=[
Expand Down
3 changes: 2 additions & 1 deletion comfy_extras/nodes_images.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,10 @@ class ImageCropV2(IO.ComfyNode):
def define_schema(cls):
return IO.Schema(
node_id="ImageCropV2",
search_aliases=["trim"],
search_aliases=["crop", "cut", "trim"],
display_name="Crop Image",
category="image/transform",
description = "Crop an image to the specified dimensions.",
essentials_category="Image Tools",
has_intermediate_output=True,
inputs=[
Expand Down
8 changes: 4 additions & 4 deletions comfy_extras/nodes_lt_audio.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ class LTXVAudioVAELoader(io.ComfyNode):
def define_schema(cls) -> io.Schema:
return io.Schema(
node_id="LTXVAudioVAELoader",
display_name="LTXV Audio VAE Loader",
category="audio",
display_name="Load LTXV Audio VAE",
category="loaders",
inputs=[
io.Combo.Input(
"ckpt_name",
Expand Down Expand Up @@ -40,7 +40,7 @@ def define_schema(cls) -> io.Schema:
return io.Schema(
node_id="LTXVAudioVAEEncode",
display_name="LTXV Audio VAE Encode",
category="audio",
category="latent/audio",
inputs=[
io.Audio.Input("audio", tooltip="The audio to be encoded."),
io.Vae.Input(
Expand All @@ -63,7 +63,7 @@ def define_schema(cls) -> io.Schema:
return io.Schema(
node_id="LTXVAudioVAEDecode",
display_name="LTXV Audio VAE Decode",
category="audio",
category="latent/audio",
inputs=[
io.Latent.Input("samples", tooltip="The latent to be decoded."),
io.Vae.Input(
Expand Down
35 changes: 21 additions & 14 deletions comfy_extras/nodes_mediapipe.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
from comfy_extras.mediapipe.face_geometry import transformation_matrix_from_detection


FaceLandmarkerType = io.Custom("FACE_LANDMARKER")
FaceDetectionType = io.Custom("FACE_DETECTION_MODEL")
FaceLandmarksType = io.Custom("FACE_LANDMARKS")

_CANONICAL_KEYS = ("canonical_vertices", "procrustes_indices", "procrustes_weights")
Expand Down Expand Up @@ -204,18 +204,19 @@ class LoadMediaPipeFaceLandmarker(io.ComfyNode):
def define_schema(cls):
return io.Schema(
node_id="LoadMediaPipeFaceLandmarker",
display_name="Load MediaPipe Face Landmarker",
search_aliases=["face", "facial", "mediapipe", "face landmark", "face mesh", "blazeface", "face detection"],
display_name="Load Face Detection Model (MediaPipe)",
category="loaders",
inputs=[
io.Combo.Input("model_name", options=folder_paths.get_filename_list("mediapipe"),
tooltip="Face Landmarker safetensors from models/mediapipe/."),
io.Combo.Input("model_name", options=folder_paths.get_filename_list("detection"),
tooltip="Face detection model from models/detection/."),
],
outputs=[FaceLandmarkerType.Output()],
outputs=[FaceDetectionType.Output()],
)

@classmethod
def execute(cls, model_name) -> io.NodeOutput:
sd = comfy.utils.load_torch_file(folder_paths.get_full_path_or_raise("mediapipe", model_name), safe_load=True)
sd = comfy.utils.load_torch_file(folder_paths.get_full_path_or_raise("detection", model_name), safe_load=True)
wrapper = FaceLandmarkerModel(sd)
return io.NodeOutput(wrapper)

Expand All @@ -234,10 +235,12 @@ class MediaPipeFaceLandmarker(io.ComfyNode):
def define_schema(cls):
return io.Schema(
node_id="MediaPipeFaceLandmarker",
display_name="MediaPipe Face Landmarker",
search_aliases=["face", "facial", "mediapipe", "face landmark", "face mesh", "blazeface", "face detection"],
display_name="Detect Face Landmarks (MediaPipe)",
category="image/detection",
description="Detects facial landmarks using MediaPipe model.",
inputs=[
FaceLandmarkerType.Input("face_landmarker"),
FaceDetectionType.Input("face_detection_model"),
io.Image.Input("image"),
io.Combo.Input("detector_variant", options=["short", "full", "both"], default="short",
tooltip="Face detector range. 'short' is tuned for close-up faces "
Expand All @@ -261,9 +264,9 @@ def define_schema(cls):
)

@classmethod
def execute(cls, face_landmarker, image, detector_variant, num_faces, min_confidence,
def execute(cls, face_detection_model, image, detector_variant, num_faces, min_confidence,
missing_frame_fallback) -> io.NodeOutput:
canonical = face_landmarker.canonical_data
canonical = face_detection_model.canonical_data
img_np = _image_to_uint8(image)
B, H, W = img_np.shape[:3]
chunk = 16
Expand All @@ -276,7 +279,7 @@ def _run(variant: str) -> list[list[dict]]:
with tqdm(total=B, desc=f"MediaPipe Face Landmarker ({variant})") as tq:
for i in range(0, B, chunk):
end = min(i + chunk, B)
res.extend(face_landmarker.detect_batch(
res.extend(face_detection_model.detect_batch(
[img_np[bi] for bi in range(i, end)],
num_faces=int(num_faces),
score_thresh=float(min_confidence),
Expand Down Expand Up @@ -306,7 +309,7 @@ def _run(variant: str) -> list[list[dict]]:
per_bb.append({"x": x1, "y": y1, "width": x2 - x1, "height": y2 - y1, "label": "face", "score": float(f["score"])})
bboxes.append(per_bb)
return io.NodeOutput({"frames": frames, "image_size": (H, W),
"connection_sets": face_landmarker.connection_sets}, bboxes)
"connection_sets": face_detection_model.connection_sets}, bboxes)


# Topology keys unioned by the 'all' connections preset (contour parts + irises + nose).
Expand All @@ -332,8 +335,10 @@ class MediaPipeFaceMeshVisualize(io.ComfyNode):
def define_schema(cls):
return io.Schema(
node_id="MediaPipeFaceMeshVisualize",
display_name="MediaPipe Face Mesh Visualize",
search_aliases=["face", "facial", "mediapipe", "face landmark", "face mesh", "blazeface", "face detection", "visualize"],
display_name="Visualize Face Landmarks (MediaPipe)",
category="image/detection",
description="Draws face landmarks mesh on the input image.",
inputs=[
FaceLandmarksType.Input("face_landmarks"),
io.Image.Input("image", optional=True, tooltip="If not connected, a black canvas will be used."),
Expand Down Expand Up @@ -443,8 +448,10 @@ class MediaPipeFaceMask(io.ComfyNode):
def define_schema(cls):
return io.Schema(
node_id="MediaPipeFaceMask",
display_name="MediaPipe Face Mask",
search_aliases=["face", "facial", "mediapipe", "face mask", "blazeface", "face detection", "visualize"],
display_name="Draw Face Mask (MediaPipe)",
category="image/detection",
description="Draws a mask from face landmarks.",
inputs=[
FaceLandmarksType.Input("face_landmarks"),
io.DynamicCombo.Input(
Expand Down
16 changes: 12 additions & 4 deletions comfy_extras/nodes_moge.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,8 +103,10 @@ class MoGePanoramaInference(io.ComfyNode):
def define_schema(cls):
return io.Schema(
node_id="MoGePanoramaInference",
display_name="MoGe Panorama Inference",
search_aliases=["moge", "panorama", "depth", "geometry", "depth estimation", "geometry estimation"],
display_name="Run MoGe Panorama Inference",
category="image/geometry_estimation",
description="Run MoGe on an equirectangular panorama by splitting it into 12 perspective views, running inference on each, and merging the results into a single depth map.",
inputs=[
MoGeModelType.Input("moge_model"),
io.Image.Input("image", tooltip="Equirectangular panorama (any aspect)."),
Expand Down Expand Up @@ -222,7 +224,9 @@ class MoGeInference(io.ComfyNode):
def define_schema(cls):
return io.Schema(
node_id="MoGeInference",
display_name="MoGe Inference",
search_aliases=["moge", "depth", "geometry", "depth estimation", "geometry estimation"],
display_name="Run MoGe Inference",
description="Run MoGe on a single image to estimate depth and geometry.",
category="image/geometry_estimation",
inputs=[
MoGeModelType.Input("moge_model"),
Expand Down Expand Up @@ -277,7 +281,9 @@ class MoGeRender(io.ComfyNode):
def define_schema(cls):
return io.Schema(
node_id="MoGeRender",
display_name="MoGe Render",
search_aliases=["moge", "render", "geometry", "depth", "normal"],
display_name="Render MoGe Geometry",
description="Render a depth map or normal map from geometry data",
category="image/geometry_estimation",
inputs=[
MoGeGeometry.Input("moge_geometry"),
Expand Down Expand Up @@ -342,7 +348,9 @@ class MoGePointMapToMesh(io.ComfyNode):
def define_schema(cls):
return io.Schema(
node_id="MoGePointMapToMesh",
display_name="MoGe Point Map to Mesh",
search_aliases=["moge", "mesh", "geometry", "point map"],
display_name="Convert MoGe Point Map to Mesh",
description="Convert a MoGe point map into a 3D mesh.",
category="image/geometry_estimation",
inputs=[
MoGeGeometry.Input("moge_geometry"),
Expand Down
2 changes: 1 addition & 1 deletion folder_paths.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@

folder_names_and_paths["optical_flow"] = ([os.path.join(models_dir, "optical_flow")], supported_pt_extensions)

folder_names_and_paths["mediapipe"] = ([os.path.join(models_dir, "mediapipe")], supported_pt_extensions)
folder_names_and_paths["detection"] = ([os.path.join(models_dir, "detection")], supported_pt_extensions)

output_directory = os.path.join(base_path, "output")
temp_directory = os.path.join(base_path, "temp")
Expand Down
65 changes: 29 additions & 36 deletions openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1556,12 +1556,6 @@ paths:
type: string
enum: [asc, desc]
description: Sort direction
- name: job_ids
in: query
schema:
type: string
x-runtime: [cloud]
description: "[cloud-only] Comma-separated UUIDs to filter assets by associated job."
- name: include_public
in: query
schema:
Expand Down Expand Up @@ -2514,37 +2508,25 @@ paths:

/api/assets/import:
post:
operationId: importAssets
operationId: importPublishedAssets
tags: [assets]
summary: Import assets from external URLs
description: "[cloud-only] Imports one or more assets from external URLs into the cloud asset store."
summary: "[cloud-only] Import published assets into the caller's library"
description: |
[cloud-only] Imports the specified published assets into the caller's asset library. New DB records reference the same storage objects; no file copying occurs. Assets the caller already owns (by hash) are deduplicated. The `id` field on each returned `AssetInfo` is the caller's newly-created private asset ID, not the published asset ID supplied in the request.
x-runtime: [cloud]
requestBody:
required: true
content:
application/json:
schema:
type: object
required:
- imports
properties:
imports:
type: array
items:
$ref: "#/components/schemas/AssetImportRequest"
description: Assets to import
$ref: "#/components/schemas/ImportPublishedAssetsRequest"
responses:
"200":
description: Import initiated
description: Successfully imported assets
content:
application/json:
schema:
type: object
properties:
assets:
type: array
items:
$ref: "#/components/schemas/Asset"
$ref: "#/components/schemas/ImportPublishedAssetsResponse"
"400":
description: Bad request
content:
Expand Down Expand Up @@ -7379,24 +7361,35 @@ components:
type: string
description: Target path on the runtime filesystem

AssetImportRequest:
ImportPublishedAssetsRequest:
type: object
x-runtime: [cloud]
description: "[cloud-only] A single asset to import from an external URL."
description: "[cloud-only] Request body for importing published assets into the caller's library."
required:
- url
- published_asset_ids
properties:
url:
type: string
format: uri
description: URL of the asset to import
name:
type: string
description: Display name for the imported asset
tags:
published_asset_ids:
type: array
description: IDs of published assets (inputs and models) to import.
items:
type: string
share_id:
type: string
nullable: true
description: |
Optional. Share ID of the published workflow these assets belong to. When provided (non-null, non-empty): all `published_asset_ids` must belong to this share's workflow version; returns 400 if the share is not found or any asset does not belong to it. When omitted, null, or empty string: no share-scoped validation is performed and the assets are validated only against global rules (preserved for clients that have not yet adopted `share_id`).

ImportPublishedAssetsResponse:
type: object
x-runtime: [cloud]
description: "[cloud-only] Response after importing published assets. Each returned `AssetInfo.id` is the caller's newly-created private asset ID, not the published asset ID supplied in the request."
required:
- assets
properties:
assets:
type: array
items:
$ref: "#/components/schemas/AssetInfo"

RemoteAssetMetadata:
type: object
Expand Down
Loading