[pull] master from Comfy-Org:master by pull[bot] · Pull Request #1132 · code/app-python-comfyui · GitHub
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
1 change: 1 addition & 0 deletions comfy_api_nodes/apis/openrouter.py
9 changes: 7 additions & 2 deletions comfy_api_nodes/nodes_bytedance.py
Original file line number Diff line number Diff line change
Expand Up @@ -3362,6 +3362,7 @@ async def execute(
cls,
ApiEndpoint(path="/proxy/byteplus/api/v3/tts/create", method="POST"),
response_model=SeedAudioResponse,
asset_urls=True,
data=SeedAudioRequest(
model=model,
text_prompt=text_prompt,
Expand All @@ -3374,11 +3375,15 @@ async def execute(
),
),
)
if not response.audio:
if response.audio:
audio_bytes = base64.b64decode(response.audio)
elif response.url:
audio_bytes = (await download_url_as_bytesio(response.url, cls=cls)).getvalue()
else:
raise Exception(
f"Seed Audio returned no audio (code={response.code}): {response.message}"
)
return IO.NodeOutput(audio_bytes_to_audio_input(base64.b64decode(response.audio)))
return IO.NodeOutput(audio_bytes_to_audio_input(audio_bytes))


_VCUBE_ENHANCE_VIDEO_ENDPOINT = ApiEndpoint(path="/proxy/byteplusmediakit/api/v1/tools/enhance-video", method="POST")
Expand Down
5 changes: 5 additions & 0 deletions comfy_api_nodes/nodes_elevenlabs.py
Original file line number Diff line number Diff line change
Expand Up @@ -404,6 +404,7 @@ async def execute(
),
data=request,
as_binary=True,
asset_urls=True,
)
return IO.NodeOutput(audio_bytes_to_audio_input(response))

Expand Down Expand Up @@ -449,6 +450,7 @@ async def execute(
files={"audio": ("audio.mp4", audio_bytes_io, "audio/mp4")},
content_type="multipart/form-data",
as_binary=True,
asset_urls=True,
)
return IO.NodeOutput(audio_bytes_to_audio_input(response))

Expand Down Expand Up @@ -545,6 +547,7 @@ async def execute(
loop=model.get("loop", None),
),
as_binary=True,
asset_urls=True,
)
return IO.NodeOutput(audio_bytes_to_audio_input(response))

Expand Down Expand Up @@ -762,6 +765,7 @@ async def execute(
files={"audio": ("audio.mp4", audio_bytes_io.getvalue(), "audio/mp4")},
content_type="multipart/form-data",
as_binary=True,
asset_urls=True,
)
return IO.NodeOutput(audio_bytes_to_audio_input(response))

Expand Down Expand Up @@ -901,6 +905,7 @@ async def execute(
),
data=request,
as_binary=True,
asset_urls=True,
)
return IO.NodeOutput(audio_bytes_to_audio_input(response))

Expand Down
1 change: 1 addition & 0 deletions comfy_api_nodes/nodes_fishaudio.py
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,7 @@ async def execute(
),
data=request,
as_binary=True,
asset_urls=True,
)
return IO.NodeOutput(audio_bytes_to_audio_input(response))

Expand Down
9 changes: 8 additions & 1 deletion comfy_api_nodes/nodes_gemini.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from fnmatch import fnmatch
from io import BytesIO
from typing import Any, Literal
from urllib.parse import urlparse

import torch
from typing_extensions import override
Expand Down Expand Up @@ -277,8 +278,12 @@ async def get_video_from_interaction(
)


GEMINI_FILES_HOST = "generativelanguage.googleapis.com"


async def download_interaction_video(uri: str, cls: type[IO.ComfyNode] | None = None) -> InputImpl.VideoFromFile:
if "/files/" not in uri:
parsed = urlparse(uri)
if parsed.netloc != GEMINI_FILES_HOST or "/files/" not in parsed.path:
return await download_url_to_video_output(uri, cls=cls)
name = uri.split("?", 1)[0].rsplit("/files/", 1)[-1].split(":", 1)[0]
await poll_op(
Expand Down Expand Up @@ -1921,6 +1926,7 @@ async def execute(cls, model: dict, seed: int) -> IO.NodeOutput:
),
),
response_model=GeminiInteraction,
asset_urls=True,
)
if interaction.status != "completed":
model_message = get_text_from_interaction(interaction).strip()
Expand Down Expand Up @@ -2181,6 +2187,7 @@ async def execute(cls, model: dict) -> IO.NodeOutput:
response_format=response_format,
),
response_model=GeminiInteraction,
asset_urls=True,
)
if interaction.status != "completed":
model_message = get_text_from_interaction(interaction).strip()
Expand Down
4 changes: 1 addition & 3 deletions comfy_api_nodes/nodes_heygen.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import uuid

import torch
from typing_extensions import override

Expand Down Expand Up @@ -70,7 +68,7 @@ async def _create_and_poll_video(cls: type[IO.ComfyNode], payload: dict) -> dict
"""POST a /v3/videos payload, poll until terminal, and return the final video data."""
created = await sync_op_raw(
cls,
ApiEndpoint(path=_VIDEOS_PATH, method="POST", headers={"Idempotency-Key": uuid.uuid4().hex}),
ApiEndpoint(path=_VIDEOS_PATH, method="POST"),
data=payload,
)
video_id = (created.get("data") or {}).get("video_id")
Expand Down
4 changes: 0 additions & 4 deletions comfy_api_nodes/nodes_ideogram.py
Original file line number Diff line number Diff line change
Expand Up @@ -356,7 +356,6 @@ async def execute(
data=edit_request,
files=files,
content_type="multipart/form-data",
max_retries=1,
)

elif image is not None or mask is not None:
Expand Down Expand Up @@ -400,7 +399,6 @@ async def execute(
data=gen_request,
files=files if files else None,
content_type="multipart/form-data",
max_retries=1,
)

if not response.data or len(response.data) == 0:
Expand Down Expand Up @@ -514,7 +512,6 @@ async def execute(
resolution=resolution.split(" ")[0] if resolution != "Auto" else None,
rendering_speed=rendering_speed,
),
max_retries=1,
)

if not response.data or len(response.data) == 0:
Expand Down Expand Up @@ -649,7 +646,6 @@ async def execute(
ApiEndpoint(path="/proxy/ideogram/text-to-image/p-image-ideogram", method="POST"),
response_model=IdeogramGenerateResponse,
data=request,
max_retries=1,
)
if not response.data:
raise Exception("No images were generated in the response")
Expand Down
1 change: 0 additions & 1 deletion comfy_api_nodes/nodes_krea.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ async def _upload_image_to_krea_assets(cls: type[IO.ComfyNode], image: Input.Ima
response_model=KreaAssetResponse,
files=[("file", (img_io.name, img_io, "image/png"))],
content_type="multipart/form-data",
max_retries=1,
wait_label="Uploading reference",
)
return response.image_url
Expand Down
1 change: 0 additions & 1 deletion comfy_api_nodes/nodes_ltxv.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,6 @@ async def _v25_submit_and_poll(cls: type[IO.ComfyNode], route: str, data: BaseMo
ApiEndpoint(f"/proxy/ltx/v2/{route}", "POST"),
response_model=Ltx25SubmitResponse,
data=data,
max_retries=1,
)
job = await poll_op(
cls,
Expand Down
23 changes: 14 additions & 9 deletions comfy_api_nodes/nodes_meta.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
from comfy_api_nodes.util import (
ApiEndpoint,
bytesio_to_image_tensor,
download_url_to_image_tensor,
pad_images_to_common_channels,
sync_op,
upload_images_to_comfyapi,
validate_string,
Expand Down Expand Up @@ -57,15 +59,16 @@ def _size(aspect_ratio: str) -> str | None:
return None if aspect_ratio == "auto" else aspect_ratio.replace(":", "x")


def _decode_images(response: MuseImageResponse) -> torch.Tensor:
images = [
bytesio_to_image_tensor(BytesIO(base64.b64decode(item.b64_json)))
for item in response.data
if item.b64_json
]
async def _decode_images(cls: type[IO.ComfyNode], response: MuseImageResponse) -> torch.Tensor:
images = []
for item in response.data:
if item.b64_json:
images.append(bytesio_to_image_tensor(BytesIO(base64.b64decode(item.b64_json))))
elif item.url:
images.append(await download_url_to_image_tensor(item.url, cls=cls))
if not images:
raise Exception("The response contains no images.")
return torch.cat(images)
return torch.cat(pad_images_to_common_channels(images))


def _reasoning_strength_input() -> IO.Combo.Input:
Expand Down Expand Up @@ -220,6 +223,7 @@ async def execute(cls, model: dict):
cls,
ApiEndpoint(path=GENERATIONS_PATH, method="POST"),
response_model=MuseImageResponse,
asset_urls=True,
data=MuseImageRequest(
model=model["model"],
prompt=model["prompt"],
Expand All @@ -228,7 +232,7 @@ async def execute(cls, model: dict):
tool_enablement=_tool_enablement(model),
),
)
return IO.NodeOutput(_decode_images(response))
return IO.NodeOutput(await _decode_images(cls, response))


class MetaMuseImageEditApi(IO.ComfyNode):
Expand Down Expand Up @@ -280,6 +284,7 @@ async def execute(cls, model: dict):
cls,
ApiEndpoint(path=EDITS_PATH, method="POST"),
response_model=MuseImageResponse,
asset_urls=True,
data=MuseImageEditRequest(
model=model["model"],
prompt=prompt,
Expand All @@ -289,7 +294,7 @@ async def execute(cls, model: dict):
images=[MuseImageInput(image_url=url) for url in urls],
),
)
return IO.NodeOutput(_decode_images(response))
return IO.NodeOutput(await _decode_images(cls, response))


class MetaApiExtension(ComfyExtension):
Expand Down
15 changes: 11 additions & 4 deletions comfy_api_nodes/nodes_openai.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,12 +84,15 @@ class SupportedOpenAIModel(str, Enum):
}


async def validate_and_cast_response(response, timeout: int = None) -> torch.Tensor:
async def validate_and_cast_response(
response, timeout: int = None, cls: type[IO.ComfyNode] = None
) -> torch.Tensor:
"""Validates and casts a response to a torch.Tensor.

Args:
response: The response to validate and cast.
timeout: Request timeout in seconds. Defaults to None (no timeout).
cls: The calling node class; required for relative `/proxy/` URLs so they can be expanded and authenticated.

Returns:
A torch.Tensor of shape (N, H, W, C) with all returned images; images whose
Expand All @@ -112,7 +115,7 @@ async def validate_and_cast_response(response, timeout: int = None) -> torch.Ten
img_io = BytesIO(base64.b64decode(img_data.b64_json))
elif img_data.url:
img_io = BytesIO()
await download_url_to_bytesio(img_data.url, img_io, timeout=timeout)
await download_url_to_bytesio(img_data.url, img_io, timeout=timeout, cls=cls)
else:
raise ValueError("Invalid image payload – neither URL nor base64 data present.")

Expand Down Expand Up @@ -367,6 +370,7 @@ async def execute(
cls,
ApiEndpoint(path="/proxy/openai/images/edits", method="POST"),
response_model=OpenAIImageGenerationResponse,
asset_urls=True,
data=OpenAIImageEditRequest(
model=model,
prompt=prompt,
Expand All @@ -385,6 +389,7 @@ async def execute(
cls,
ApiEndpoint(path="/proxy/openai/images/generations", method="POST"),
response_model=OpenAIImageGenerationResponse,
asset_urls=True,
data=OpenAIImageGenerationRequest(
model=model,
prompt=prompt,
Expand All @@ -396,7 +401,7 @@ async def execute(
moderation="low",
),
)
return IO.NodeOutput(await validate_and_cast_response(response))
return IO.NodeOutput(await validate_and_cast_response(response, cls=cls))


GPT_IMAGE_QUALITIES = ("low", "medium", "high")
Expand Down Expand Up @@ -732,6 +737,7 @@ async def execute(
cls,
ApiEndpoint(path="/proxy/openai/images/edits", method="POST"),
response_model=OpenAIImageGenerationResponse,
asset_urls=True,
data=OpenAIImageEditRequest(
model=model_id,
prompt=prompt,
Expand All @@ -749,6 +755,7 @@ async def execute(
cls,
ApiEndpoint(path="/proxy/openai/images/generations", method="POST"),
response_model=OpenAIImageGenerationResponse,
asset_urls=True,
data=OpenAIImageGenerationRequest(
model=model_id,
prompt=prompt,
Expand All @@ -759,7 +766,7 @@ async def execute(
moderation="low",
),
)
return IO.NodeOutput(await validate_and_cast_response(response))
return IO.NodeOutput(await validate_and_cast_response(response, cls=cls))


class OpenAIChatNode(IO.ComfyNode):
Expand Down
22 changes: 14 additions & 8 deletions comfy_api_nodes/nodes_openrouter.py
Loading
Loading