mirror of
https://github.com/comfyanonymous/ComfyUI.git
synced 2026-01-25 05:40:15 +08:00
Merge branch 'comfyanonymous:master' into master
This commit is contained in:
commit
0ff5eda8a5
@ -782,9 +782,11 @@ class PollingOperation(Generic[T, R]):
|
|||||||
poll_endpoint: ApiEndpoint[EmptyRequest, R],
|
poll_endpoint: ApiEndpoint[EmptyRequest, R],
|
||||||
completed_statuses: list[str],
|
completed_statuses: list[str],
|
||||||
failed_statuses: list[str],
|
failed_statuses: list[str],
|
||||||
|
*,
|
||||||
status_extractor: Callable[[R], Optional[str]],
|
status_extractor: Callable[[R], Optional[str]],
|
||||||
progress_extractor: Callable[[R], Optional[float]] | None = None,
|
progress_extractor: Callable[[R], Optional[float]] | None = None,
|
||||||
result_url_extractor: Callable[[R], Optional[str]] | None = None,
|
result_url_extractor: Callable[[R], Optional[str]] | None = None,
|
||||||
|
price_extractor: Callable[[R], Optional[float]] | None = None,
|
||||||
request: Optional[T] = None,
|
request: Optional[T] = None,
|
||||||
api_base: str | None = None,
|
api_base: str | None = None,
|
||||||
auth_token: Optional[str] = None,
|
auth_token: Optional[str] = None,
|
||||||
@ -815,10 +817,12 @@ class PollingOperation(Generic[T, R]):
|
|||||||
self.status_extractor = status_extractor or (lambda x: getattr(x, "status", None))
|
self.status_extractor = status_extractor or (lambda x: getattr(x, "status", None))
|
||||||
self.progress_extractor = progress_extractor
|
self.progress_extractor = progress_extractor
|
||||||
self.result_url_extractor = result_url_extractor
|
self.result_url_extractor = result_url_extractor
|
||||||
|
self.price_extractor = price_extractor
|
||||||
self.node_id = node_id
|
self.node_id = node_id
|
||||||
self.completed_statuses = completed_statuses
|
self.completed_statuses = completed_statuses
|
||||||
self.failed_statuses = failed_statuses
|
self.failed_statuses = failed_statuses
|
||||||
self.final_response: Optional[R] = None
|
self.final_response: Optional[R] = None
|
||||||
|
self.extracted_price: Optional[float] = None
|
||||||
|
|
||||||
async def execute(self, client: Optional[ApiClient] = None) -> R:
|
async def execute(self, client: Optional[ApiClient] = None) -> R:
|
||||||
owns_client = client is None
|
owns_client = client is None
|
||||||
@ -840,6 +844,8 @@ class PollingOperation(Generic[T, R]):
|
|||||||
def _display_text_on_node(self, text: str):
|
def _display_text_on_node(self, text: str):
|
||||||
if not self.node_id:
|
if not self.node_id:
|
||||||
return
|
return
|
||||||
|
if self.extracted_price is not None:
|
||||||
|
text = f"Price: {self.extracted_price}$\n{text}"
|
||||||
PromptServer.instance.send_progress_text(text, self.node_id)
|
PromptServer.instance.send_progress_text(text, self.node_id)
|
||||||
|
|
||||||
def _display_time_progress_on_node(self, time_completed: int | float):
|
def _display_time_progress_on_node(self, time_completed: int | float):
|
||||||
@ -877,9 +883,7 @@ class PollingOperation(Generic[T, R]):
|
|||||||
try:
|
try:
|
||||||
logging.debug("[DEBUG] Polling attempt #%s", poll_count)
|
logging.debug("[DEBUG] Polling attempt #%s", poll_count)
|
||||||
|
|
||||||
request_dict = (
|
request_dict = None if self.request is None else self.request.model_dump(exclude_none=True)
|
||||||
None if self.request is None else self.request.model_dump(exclude_none=True)
|
|
||||||
)
|
|
||||||
|
|
||||||
if poll_count == 1:
|
if poll_count == 1:
|
||||||
logging.debug(
|
logging.debug(
|
||||||
@ -912,6 +916,11 @@ class PollingOperation(Generic[T, R]):
|
|||||||
if new_progress is not None:
|
if new_progress is not None:
|
||||||
progress.update_absolute(new_progress, total=PROGRESS_BAR_MAX)
|
progress.update_absolute(new_progress, total=PROGRESS_BAR_MAX)
|
||||||
|
|
||||||
|
if self.price_extractor:
|
||||||
|
price = self.price_extractor(response_obj)
|
||||||
|
if price is not None:
|
||||||
|
self.extracted_price = price
|
||||||
|
|
||||||
if status == TaskStatus.COMPLETED:
|
if status == TaskStatus.COMPLETED:
|
||||||
message = "Task completed successfully"
|
message = "Task completed successfully"
|
||||||
if self.result_url_extractor:
|
if self.result_url_extractor:
|
||||||
|
|||||||
@ -1,19 +1,22 @@
|
|||||||
from __future__ import annotations
|
from typing import Optional
|
||||||
|
|
||||||
from typing import List, Optional
|
|
||||||
|
|
||||||
from comfy_api_nodes.apis import GeminiGenerationConfig, GeminiContent, GeminiSafetySetting, GeminiSystemInstructionContent, GeminiTool, GeminiVideoMetadata
|
from comfy_api_nodes.apis import GeminiGenerationConfig, GeminiContent, GeminiSafetySetting, GeminiSystemInstructionContent, GeminiTool, GeminiVideoMetadata
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
|
||||||
|
class GeminiImageConfig(BaseModel):
|
||||||
|
aspectRatio: Optional[str] = None
|
||||||
|
|
||||||
|
|
||||||
class GeminiImageGenerationConfig(GeminiGenerationConfig):
|
class GeminiImageGenerationConfig(GeminiGenerationConfig):
|
||||||
responseModalities: Optional[List[str]] = None
|
responseModalities: Optional[list[str]] = None
|
||||||
|
imageConfig: Optional[GeminiImageConfig] = None
|
||||||
|
|
||||||
|
|
||||||
class GeminiImageGenerateContentRequest(BaseModel):
|
class GeminiImageGenerateContentRequest(BaseModel):
|
||||||
contents: List[GeminiContent]
|
contents: list[GeminiContent]
|
||||||
generationConfig: Optional[GeminiImageGenerationConfig] = None
|
generationConfig: Optional[GeminiImageGenerationConfig] = None
|
||||||
safetySettings: Optional[List[GeminiSafetySetting]] = None
|
safetySettings: Optional[list[GeminiSafetySetting]] = None
|
||||||
systemInstruction: Optional[GeminiSystemInstructionContent] = None
|
systemInstruction: Optional[GeminiSystemInstructionContent] = None
|
||||||
tools: Optional[List[GeminiTool]] = None
|
tools: Optional[list[GeminiTool]] = None
|
||||||
videoMetadata: Optional[GeminiVideoMetadata] = None
|
videoMetadata: Optional[GeminiVideoMetadata] = None
|
||||||
|
|||||||
@ -26,7 +26,7 @@ from comfy_api_nodes.apis import (
|
|||||||
GeminiPart,
|
GeminiPart,
|
||||||
GeminiMimeType,
|
GeminiMimeType,
|
||||||
)
|
)
|
||||||
from comfy_api_nodes.apis.gemini_api import GeminiImageGenerationConfig, GeminiImageGenerateContentRequest
|
from comfy_api_nodes.apis.gemini_api import GeminiImageGenerationConfig, GeminiImageGenerateContentRequest, GeminiImageConfig
|
||||||
from comfy_api_nodes.apis.client import (
|
from comfy_api_nodes.apis.client import (
|
||||||
ApiEndpoint,
|
ApiEndpoint,
|
||||||
HttpMethod,
|
HttpMethod,
|
||||||
@ -63,6 +63,7 @@ class GeminiImageModel(str, Enum):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
gemini_2_5_flash_image_preview = "gemini-2.5-flash-image-preview"
|
gemini_2_5_flash_image_preview = "gemini-2.5-flash-image-preview"
|
||||||
|
gemini_2_5_flash_image = "gemini-2.5-flash-image"
|
||||||
|
|
||||||
|
|
||||||
def get_gemini_endpoint(
|
def get_gemini_endpoint(
|
||||||
@ -538,7 +539,7 @@ class GeminiImage(ComfyNodeABC):
|
|||||||
{
|
{
|
||||||
"tooltip": "The Gemini model to use for generating responses.",
|
"tooltip": "The Gemini model to use for generating responses.",
|
||||||
"options": [model.value for model in GeminiImageModel],
|
"options": [model.value for model in GeminiImageModel],
|
||||||
"default": GeminiImageModel.gemini_2_5_flash_image_preview.value,
|
"default": GeminiImageModel.gemini_2_5_flash_image.value,
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
"seed": (
|
"seed": (
|
||||||
@ -579,6 +580,14 @@ class GeminiImage(ComfyNodeABC):
|
|||||||
# "tooltip": "How many images to generate",
|
# "tooltip": "How many images to generate",
|
||||||
# },
|
# },
|
||||||
# ),
|
# ),
|
||||||
|
"aspect_ratio": (
|
||||||
|
IO.COMBO,
|
||||||
|
{
|
||||||
|
"tooltip": "Defaults to matching the output image size to that of your input image, or otherwise generates 1:1 squares.",
|
||||||
|
"options": ["auto", "1:1", "2:3", "3:2", "3:4", "4:3", "4:5", "5:4", "9:16", "16:9", "21:9"],
|
||||||
|
"default": "auto",
|
||||||
|
},
|
||||||
|
),
|
||||||
},
|
},
|
||||||
"hidden": {
|
"hidden": {
|
||||||
"auth_token": "AUTH_TOKEN_COMFY_ORG",
|
"auth_token": "AUTH_TOKEN_COMFY_ORG",
|
||||||
@ -600,15 +609,17 @@ class GeminiImage(ComfyNodeABC):
|
|||||||
images: Optional[IO.IMAGE] = None,
|
images: Optional[IO.IMAGE] = None,
|
||||||
files: Optional[list[GeminiPart]] = None,
|
files: Optional[list[GeminiPart]] = None,
|
||||||
n=1,
|
n=1,
|
||||||
|
aspect_ratio: str = "auto",
|
||||||
unique_id: Optional[str] = None,
|
unique_id: Optional[str] = None,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
):
|
):
|
||||||
# Validate inputs
|
|
||||||
validate_string(prompt, strip_whitespace=True, min_length=1)
|
validate_string(prompt, strip_whitespace=True, min_length=1)
|
||||||
# Create parts list with text prompt as the first part
|
|
||||||
parts: list[GeminiPart] = [create_text_part(prompt)]
|
parts: list[GeminiPart] = [create_text_part(prompt)]
|
||||||
|
|
||||||
# Add other modal parts
|
if not aspect_ratio:
|
||||||
|
aspect_ratio = "auto" # for backward compatability with old workflows; to-do remove this in December
|
||||||
|
image_config = GeminiImageConfig(aspectRatio=aspect_ratio)
|
||||||
|
|
||||||
if images is not None:
|
if images is not None:
|
||||||
image_parts = create_image_parts(images)
|
image_parts = create_image_parts(images)
|
||||||
parts.extend(image_parts)
|
parts.extend(image_parts)
|
||||||
@ -625,7 +636,8 @@ class GeminiImage(ComfyNodeABC):
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
generationConfig=GeminiImageGenerationConfig(
|
generationConfig=GeminiImageGenerationConfig(
|
||||||
responseModalities=["TEXT","IMAGE"]
|
responseModalities=["TEXT","IMAGE"],
|
||||||
|
imageConfig=None if aspect_ratio == "auto" else image_config,
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
auth_kwargs=kwargs,
|
auth_kwargs=kwargs,
|
||||||
|
|||||||
@ -73,6 +73,7 @@ from comfy_api_nodes.util.validation_utils import (
|
|||||||
validate_video_dimensions,
|
validate_video_dimensions,
|
||||||
validate_video_duration,
|
validate_video_duration,
|
||||||
)
|
)
|
||||||
|
from comfy_api.input_impl import VideoFromFile
|
||||||
from comfy_api.input.basic_types import AudioInput
|
from comfy_api.input.basic_types import AudioInput
|
||||||
from comfy_api.input.video_types import VideoInput
|
from comfy_api.input.video_types import VideoInput
|
||||||
from comfy_api.latest import ComfyExtension, io as comfy_io
|
from comfy_api.latest import ComfyExtension, io as comfy_io
|
||||||
@ -511,7 +512,7 @@ async def execute_video_effect(
|
|||||||
image_1: torch.Tensor,
|
image_1: torch.Tensor,
|
||||||
image_2: Optional[torch.Tensor] = None,
|
image_2: Optional[torch.Tensor] = None,
|
||||||
model_mode: Optional[KlingVideoGenMode] = None,
|
model_mode: Optional[KlingVideoGenMode] = None,
|
||||||
) -> comfy_io.NodeOutput:
|
) -> tuple[VideoFromFile, str, str]:
|
||||||
if dual_character:
|
if dual_character:
|
||||||
request_input_field = KlingDualCharacterEffectInput(
|
request_input_field = KlingDualCharacterEffectInput(
|
||||||
model_name=model_name,
|
model_name=model_name,
|
||||||
@ -562,7 +563,7 @@ async def execute_video_effect(
|
|||||||
validate_video_result_response(final_response)
|
validate_video_result_response(final_response)
|
||||||
|
|
||||||
video = get_video_from_response(final_response)
|
video = get_video_from_response(final_response)
|
||||||
return comfy_io.NodeOutput(await download_url_to_video_output(str(video.url)), str(video.id), str(video.duration))
|
return await download_url_to_video_output(str(video.url)), str(video.id), str(video.duration)
|
||||||
|
|
||||||
|
|
||||||
async def execute_lipsync(
|
async def execute_lipsync(
|
||||||
@ -1271,7 +1272,7 @@ class KlingDualCharacterVideoEffectNode(comfy_io.ComfyNode):
|
|||||||
image_1=image_left,
|
image_1=image_left,
|
||||||
image_2=image_right,
|
image_2=image_right,
|
||||||
)
|
)
|
||||||
return video, duration
|
return comfy_io.NodeOutput(video, duration)
|
||||||
|
|
||||||
|
|
||||||
class KlingSingleImageVideoEffectNode(comfy_io.ComfyNode):
|
class KlingSingleImageVideoEffectNode(comfy_io.ComfyNode):
|
||||||
@ -1320,17 +1321,21 @@ class KlingSingleImageVideoEffectNode(comfy_io.ComfyNode):
|
|||||||
model_name: KlingSingleImageEffectModelName,
|
model_name: KlingSingleImageEffectModelName,
|
||||||
duration: KlingVideoGenDuration,
|
duration: KlingVideoGenDuration,
|
||||||
) -> comfy_io.NodeOutput:
|
) -> comfy_io.NodeOutput:
|
||||||
return await execute_video_effect(
|
return comfy_io.NodeOutput(
|
||||||
auth_kwargs={
|
*(
|
||||||
"auth_token": cls.hidden.auth_token_comfy_org,
|
await execute_video_effect(
|
||||||
"comfy_api_key": cls.hidden.api_key_comfy_org,
|
auth_kwargs={
|
||||||
},
|
"auth_token": cls.hidden.auth_token_comfy_org,
|
||||||
node_id=cls.hidden.unique_id,
|
"comfy_api_key": cls.hidden.api_key_comfy_org,
|
||||||
dual_character=False,
|
},
|
||||||
effect_scene=effect_scene,
|
node_id=cls.hidden.unique_id,
|
||||||
model_name=model_name,
|
dual_character=False,
|
||||||
duration=duration,
|
effect_scene=effect_scene,
|
||||||
image_1=image,
|
model_name=model_name,
|
||||||
|
duration=duration,
|
||||||
|
image_1=image,
|
||||||
|
)
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -17,6 +17,7 @@ from comfy_api.input_impl.video_types import VideoCodec, VideoContainer, VideoIn
|
|||||||
from comfy_api_nodes.apinode_utils import (
|
from comfy_api_nodes.apinode_utils import (
|
||||||
download_url_to_video_output,
|
download_url_to_video_output,
|
||||||
tensor_to_bytesio,
|
tensor_to_bytesio,
|
||||||
|
validate_string,
|
||||||
)
|
)
|
||||||
from comfy_api_nodes.apis import pika_defs
|
from comfy_api_nodes.apis import pika_defs
|
||||||
from comfy_api_nodes.apis.client import (
|
from comfy_api_nodes.apis.client import (
|
||||||
@ -590,6 +591,7 @@ class PikaStartEndFrameNode(comfy_io.ComfyNode):
|
|||||||
resolution: str,
|
resolution: str,
|
||||||
duration: int,
|
duration: int,
|
||||||
) -> comfy_io.NodeOutput:
|
) -> comfy_io.NodeOutput:
|
||||||
|
validate_string(prompt_text, field_name="prompt_text", min_length=1)
|
||||||
pika_files = [
|
pika_files = [
|
||||||
("keyFrames", ("image_start.png", tensor_to_bytesio(image_start), "image/png")),
|
("keyFrames", ("image_start.png", tensor_to_bytesio(image_start), "image/png")),
|
||||||
("keyFrames", ("image_end.png", tensor_to_bytesio(image_end), "image/png")),
|
("keyFrames", ("image_end.png", tensor_to_bytesio(image_end), "image/png")),
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user