mirror of
https://git.datalinker.icu/comfyanonymous/ComfyUI
synced 2026-09-04 15:57:10 +08:00
Add Minimax Video Generation + Async Task queue polling example (#6)
This commit is contained in:
parent
e7dad1d7db
commit
e772520dcd
@ -108,6 +108,8 @@ from comfy.cli_args import args
|
||||
from comfy import utils
|
||||
from . import request_logger
|
||||
|
||||
# Import models from your generated stubs
|
||||
|
||||
T = TypeVar("T", bound=BaseModel)
|
||||
R = TypeVar("R", bound=BaseModel)
|
||||
P = TypeVar("P", bound=BaseModel) # For poll response
|
||||
|
||||
513
comfy_api_nodes/apis/stubs.py
Normal file
513
comfy_api_nodes/apis/stubs.py
Normal file
@ -0,0 +1,513 @@
|
||||
# generated by datamodel-codegen:
|
||||
# filename: http://localhost:8080/openapi
|
||||
# timestamp: 2025-04-18T21:35:21+00:00
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime
|
||||
from enum import Enum
|
||||
from typing import Any, Dict, List, Optional
|
||||
from uuid import UUID
|
||||
|
||||
from pydantic import BaseModel, Field, conint, constr
|
||||
|
||||
|
||||
class ComfyNode(BaseModel):
|
||||
category: Optional[str] = Field(
|
||||
None,
|
||||
description='UI category where the node is listed, used for grouping nodes.',
|
||||
)
|
||||
comfy_node_name: Optional[str] = Field(
|
||||
None, description='Unique identifier for the node'
|
||||
)
|
||||
deprecated: Optional[bool] = Field(
|
||||
None,
|
||||
description='Indicates if the node is deprecated. Deprecated nodes are hidden in the UI.',
|
||||
)
|
||||
description: Optional[str] = Field(
|
||||
None, description="Brief description of the node's functionality or purpose."
|
||||
)
|
||||
experimental: Optional[bool] = Field(
|
||||
None,
|
||||
description='Indicates if the node is experimental, subject to changes or removal.',
|
||||
)
|
||||
function: Optional[str] = Field(
|
||||
None, description='Name of the entry-point function to execute the node.'
|
||||
)
|
||||
input_types: Optional[str] = Field(None, description='Defines input parameters')
|
||||
output_is_list: Optional[List[bool]] = Field(
|
||||
None, description='Boolean values indicating if each output is a list.'
|
||||
)
|
||||
return_names: Optional[str] = Field(
|
||||
None, description='Names of the outputs for clarity in workflows.'
|
||||
)
|
||||
return_types: Optional[str] = Field(
|
||||
None, description='Specifies the types of outputs produced by the node.'
|
||||
)
|
||||
|
||||
|
||||
class ComfyNodeCloudBuildInfo(BaseModel):
|
||||
build_id: Optional[str] = None
|
||||
location: Optional[str] = None
|
||||
project_id: Optional[str] = None
|
||||
project_number: Optional[str] = None
|
||||
|
||||
|
||||
class Customer(BaseModel):
|
||||
createdAt: Optional[datetime] = Field(
|
||||
None, description='The date and time the user was created'
|
||||
)
|
||||
email: Optional[str] = Field(None, description='The email address for this user')
|
||||
id: str = Field(..., description='The firebase UID of the user')
|
||||
name: Optional[str] = Field(None, description='The name for this user')
|
||||
updatedAt: Optional[datetime] = Field(
|
||||
None, description='The date and time the user was last updated'
|
||||
)
|
||||
|
||||
|
||||
class Error(BaseModel):
|
||||
details: Optional[List[str]] = Field(
|
||||
None,
|
||||
description='Optional detailed information about the error or hints for resolving it.',
|
||||
)
|
||||
message: Optional[str] = Field(
|
||||
None, description='A clear and concise description of the error.'
|
||||
)
|
||||
|
||||
|
||||
class ErrorResponse(BaseModel):
|
||||
error: str
|
||||
message: str
|
||||
|
||||
|
||||
class GitCommitSummary(BaseModel):
|
||||
author: Optional[str] = Field(None, description='The author of the commit')
|
||||
branch_name: Optional[str] = Field(
|
||||
None, description='The branch where the commit was made'
|
||||
)
|
||||
commit_hash: Optional[str] = Field(None, description='The hash of the commit')
|
||||
commit_name: Optional[str] = Field(None, description='The name of the commit')
|
||||
status_summary: Optional[Dict[str, str]] = Field(
|
||||
None, description='A map of operating system to status pairs'
|
||||
)
|
||||
timestamp: Optional[datetime] = Field(
|
||||
None, description='The timestamp when the commit was made'
|
||||
)
|
||||
|
||||
|
||||
class ImageRequest(BaseModel):
|
||||
aspect_ratio: Optional[str] = Field(
|
||||
None,
|
||||
description="Optional. The aspect ratio (e.g., 'ASPECT_16_9', 'ASPECT_1_1'). Cannot be used with resolution. Defaults to 'ASPECT_1_1' if unspecified.",
|
||||
)
|
||||
color_palette: Optional[Dict[str, Any]] = Field(
|
||||
None, description='Optional. Color palette object. Only for V_2, V_2_TURBO.'
|
||||
)
|
||||
magic_prompt_option: Optional[str] = Field(
|
||||
None, description="Optional. MagicPrompt usage ('AUTO', 'ON', 'OFF')."
|
||||
)
|
||||
model: Optional[str] = Field(
|
||||
None,
|
||||
description="Optional. The model used (e.g., 'V_2', 'V_2A_TURBO'). Defaults to 'V_2' if unspecified.",
|
||||
)
|
||||
negative_prompt: Optional[str] = Field(
|
||||
None,
|
||||
description='Optional. Description of what to exclude. Only for V_1, V_1_TURBO, V_2, V_2_TURBO.',
|
||||
)
|
||||
num_images: Optional[conint(ge=1, le=8)] = Field(
|
||||
1, description='Optional. Number of images to generate (1-8). Defaults to 1.'
|
||||
)
|
||||
prompt: str = Field(
|
||||
..., description='Required. The prompt to use to generate the image.'
|
||||
)
|
||||
resolution: Optional[str] = Field(
|
||||
None,
|
||||
description="Optional. Resolution (e.g., 'RESOLUTION_1024_1024'). Only for model V_2. Cannot be used with aspect_ratio.",
|
||||
)
|
||||
seed: Optional[conint(ge=0, le=2147483647)] = Field(
|
||||
None, description='Optional. A number between 0 and 2147483647.'
|
||||
)
|
||||
style_type: Optional[str] = Field(
|
||||
None,
|
||||
description="Optional. Style type ('AUTO', 'GENERAL', 'REALISTIC', 'DESIGN', 'RENDER_3D', 'ANIME'). Only for models V_2 and above.",
|
||||
)
|
||||
|
||||
|
||||
class IdeogramGenerateRequest(BaseModel):
|
||||
image_request: ImageRequest = Field(
|
||||
..., description='The image generation request parameters.'
|
||||
)
|
||||
|
||||
|
||||
class Datum(BaseModel):
|
||||
is_image_safe: Optional[bool] = Field(
|
||||
None, description='Indicates whether the image is considered safe.'
|
||||
)
|
||||
prompt: Optional[str] = Field(
|
||||
None, description='The prompt used to generate this image.'
|
||||
)
|
||||
resolution: Optional[str] = Field(
|
||||
None, description="The resolution of the generated image (e.g., '1024x1024')."
|
||||
)
|
||||
seed: Optional[int] = Field(
|
||||
None, description='The seed value used for this generation.'
|
||||
)
|
||||
style_type: Optional[str] = Field(
|
||||
None,
|
||||
description="The style type used for generation (e.g., 'REALISTIC', 'ANIME').",
|
||||
)
|
||||
url: Optional[str] = Field(None, description='URL to the generated image.')
|
||||
|
||||
|
||||
class IdeogramGenerateResponse(BaseModel):
|
||||
created: Optional[datetime] = Field(
|
||||
None, description='Timestamp when the generation was created.'
|
||||
)
|
||||
data: Optional[List[Datum]] = Field(
|
||||
None, description='Array of generated image information.'
|
||||
)
|
||||
|
||||
|
||||
class MachineStats(BaseModel):
|
||||
cpu_capacity: Optional[str] = Field(None, description='Total CPU on the machine.')
|
||||
disk_capacity: Optional[str] = Field(
|
||||
None, description='Total disk capacity on the machine.'
|
||||
)
|
||||
gpu_type: Optional[str] = Field(
|
||||
None, description='The GPU type. eg. NVIDIA Tesla K80'
|
||||
)
|
||||
initial_cpu: Optional[str] = Field(
|
||||
None, description='Initial CPU available before the job starts.'
|
||||
)
|
||||
initial_disk: Optional[str] = Field(
|
||||
None, description='Initial disk available before the job starts.'
|
||||
)
|
||||
initial_ram: Optional[str] = Field(
|
||||
None, description='Initial RAM available before the job starts.'
|
||||
)
|
||||
machine_name: Optional[str] = Field(None, description='Name of the machine.')
|
||||
memory_capacity: Optional[str] = Field(
|
||||
None, description='Total memory on the machine.'
|
||||
)
|
||||
os_version: Optional[str] = Field(
|
||||
None, description='The operating system version. eg. Ubuntu Linux 20.04'
|
||||
)
|
||||
pip_freeze: Optional[str] = Field(None, description='The pip freeze output')
|
||||
vram_time_series: Optional[Dict[str, Any]] = Field(
|
||||
None, description='Time series of VRAM usage.'
|
||||
)
|
||||
|
||||
|
||||
class MinimaxBaseResponse(BaseModel):
|
||||
status_code: int = Field(
|
||||
...,
|
||||
description='Status code. 0 indicates success, other values indicate errors.',
|
||||
)
|
||||
status_msg: str = Field(
|
||||
..., description='Specific error details or success message.'
|
||||
)
|
||||
|
||||
|
||||
class File(BaseModel):
|
||||
bytes: Optional[int] = Field(None, description='File size in bytes')
|
||||
created_at: Optional[int] = Field(
|
||||
None, description='Unix timestamp when the file was created, in seconds'
|
||||
)
|
||||
download_url: Optional[str] = Field(
|
||||
None, description='The URL to download the video'
|
||||
)
|
||||
file_id: Optional[int] = Field(None, description='Unique identifier for the file')
|
||||
filename: Optional[str] = Field(None, description='The name of the file')
|
||||
purpose: Optional[str] = Field(None, description='The purpose of using the file')
|
||||
|
||||
|
||||
class MinimaxFileRetrieveResponse(BaseModel):
|
||||
base_resp: MinimaxBaseResponse
|
||||
file: File
|
||||
|
||||
|
||||
class Status(str, Enum):
|
||||
Queueing = 'Queueing'
|
||||
Preparing = 'Preparing'
|
||||
Processing = 'Processing'
|
||||
Success = 'Success'
|
||||
Fail = 'Fail'
|
||||
|
||||
|
||||
class MinimaxTaskResultResponse(BaseModel):
|
||||
base_resp: MinimaxBaseResponse
|
||||
file_id: Optional[str] = Field(
|
||||
None,
|
||||
description='After the task status changes to Success, this field returns the file ID corresponding to the generated video.',
|
||||
)
|
||||
status: Status = Field(
|
||||
...,
|
||||
description="Task status: 'Queueing' (in queue), 'Preparing' (task is preparing), 'Processing' (generating), 'Success' (task completed successfully), or 'Fail' (task failed).",
|
||||
)
|
||||
task_id: str = Field(..., description='The task ID being queried.')
|
||||
|
||||
|
||||
class Model(str, Enum):
|
||||
T2V_01_Director = 'T2V-01-Director'
|
||||
I2V_01_Director = 'I2V-01-Director'
|
||||
S2V_01 = 'S2V-01'
|
||||
I2V_01 = 'I2V-01'
|
||||
I2V_01_live = 'I2V-01-live'
|
||||
T2V_01 = 'T2V-01'
|
||||
|
||||
|
||||
class SubjectReferenceItem(BaseModel):
|
||||
image: Optional[str] = Field(
|
||||
None, description='URL or base64 encoding of the subject reference image.'
|
||||
)
|
||||
mask: Optional[str] = Field(
|
||||
None,
|
||||
description='URL or base64 encoding of the mask for the subject reference image.',
|
||||
)
|
||||
|
||||
|
||||
class MinimaxVideoGenerationRequest(BaseModel):
|
||||
callback_url: Optional[str] = Field(
|
||||
None,
|
||||
description='Optional. URL to receive real-time status updates about the video generation task.',
|
||||
)
|
||||
first_frame_image: Optional[str] = Field(
|
||||
None,
|
||||
description='URL or base64 encoding of the first frame image. Required when model is I2V-01, I2V-01-Director, or I2V-01-live.',
|
||||
)
|
||||
model: Model = Field(
|
||||
...,
|
||||
description='Required. ID of model. Options: T2V-01-Director, I2V-01-Director, S2V-01, I2V-01, I2V-01-live, T2V-01',
|
||||
)
|
||||
prompt: Optional[constr(max_length=2000)] = Field(
|
||||
None,
|
||||
description='Description of the video. Should be less than 2000 characters. Supports camera movement instructions in [brackets].',
|
||||
)
|
||||
prompt_optimizer: Optional[bool] = Field(
|
||||
True,
|
||||
description='If true (default), the model will automatically optimize the prompt. Set to false for more precise control.',
|
||||
)
|
||||
subject_reference: Optional[List[SubjectReferenceItem]] = Field(
|
||||
None,
|
||||
description='Only available when model is S2V-01. The model will generate a video based on the subject uploaded through this parameter.',
|
||||
)
|
||||
|
||||
|
||||
class MinimaxVideoGenerationResponse(BaseModel):
|
||||
base_resp: MinimaxBaseResponse
|
||||
task_id: str = Field(
|
||||
..., description='The task ID for the asynchronous video generation task.'
|
||||
)
|
||||
|
||||
|
||||
class NodeStatus(str, Enum):
|
||||
NodeStatusActive = 'NodeStatusActive'
|
||||
NodeStatusDeleted = 'NodeStatusDeleted'
|
||||
NodeStatusBanned = 'NodeStatusBanned'
|
||||
|
||||
|
||||
class NodeVersionStatus(str, Enum):
|
||||
NodeVersionStatusActive = 'NodeVersionStatusActive'
|
||||
NodeVersionStatusDeleted = 'NodeVersionStatusDeleted'
|
||||
NodeVersionStatusBanned = 'NodeVersionStatusBanned'
|
||||
NodeVersionStatusPending = 'NodeVersionStatusPending'
|
||||
NodeVersionStatusFlagged = 'NodeVersionStatusFlagged'
|
||||
|
||||
|
||||
class NodeVersionUpdateRequest(BaseModel):
|
||||
changelog: Optional[str] = Field(
|
||||
None, description='The changelog describing the version changes.'
|
||||
)
|
||||
deprecated: Optional[bool] = Field(
|
||||
None, description='Whether the version is deprecated.'
|
||||
)
|
||||
|
||||
|
||||
class PersonalAccessToken(BaseModel):
|
||||
createdAt: Optional[datetime] = Field(
|
||||
None, description='[Output Only]The date and time the token was created.'
|
||||
)
|
||||
description: Optional[str] = Field(
|
||||
None,
|
||||
description="Optional. A more detailed description of the token's intended use.",
|
||||
)
|
||||
id: Optional[UUID] = Field(None, description='Unique identifier for the GitCommit')
|
||||
name: Optional[str] = Field(
|
||||
None,
|
||||
description='Required. The name of the token. Can be a simple description.',
|
||||
)
|
||||
token: Optional[str] = Field(
|
||||
None,
|
||||
description='[Output Only]. The personal access token. Only returned during creation.',
|
||||
)
|
||||
|
||||
|
||||
class PublisherStatus(str, Enum):
|
||||
PublisherStatusActive = 'PublisherStatusActive'
|
||||
PublisherStatusBanned = 'PublisherStatusBanned'
|
||||
|
||||
|
||||
class PublisherUser(BaseModel):
|
||||
email: Optional[str] = Field(None, description='The email address for this user.')
|
||||
id: Optional[str] = Field(None, description='The unique id for this user.')
|
||||
name: Optional[str] = Field(None, description='The name for this user.')
|
||||
|
||||
|
||||
class StorageFile(BaseModel):
|
||||
file_path: Optional[str] = Field(None, description='Path to the file in storage')
|
||||
id: Optional[UUID] = Field(
|
||||
None, description='Unique identifier for the storage file'
|
||||
)
|
||||
public_url: Optional[str] = Field(None, description='Public URL')
|
||||
|
||||
|
||||
class User(BaseModel):
|
||||
email: Optional[str] = Field(None, description='The email address for this user.')
|
||||
id: Optional[str] = Field(None, description='The unique id for this user.')
|
||||
isAdmin: Optional[bool] = Field(
|
||||
None, description='Indicates if the user has admin privileges.'
|
||||
)
|
||||
isApproved: Optional[bool] = Field(
|
||||
None, description='Indicates if the user is approved.'
|
||||
)
|
||||
name: Optional[str] = Field(None, description='The name for this user.')
|
||||
|
||||
|
||||
class WorkflowRunStatus(str, Enum):
|
||||
WorkflowRunStatusStarted = 'WorkflowRunStatusStarted'
|
||||
WorkflowRunStatusFailed = 'WorkflowRunStatusFailed'
|
||||
WorkflowRunStatusCompleted = 'WorkflowRunStatusCompleted'
|
||||
|
||||
|
||||
class ActionJobResult(BaseModel):
|
||||
action_job_id: Optional[str] = Field(
|
||||
None, description='Identifier of the job this result belongs to'
|
||||
)
|
||||
action_run_id: Optional[str] = Field(
|
||||
None, description='Identifier of the run this result belongs to'
|
||||
)
|
||||
author: Optional[str] = Field(None, description='The author of the commit')
|
||||
avg_vram: Optional[int] = Field(
|
||||
None, description='The average VRAM used by the job'
|
||||
)
|
||||
branch_name: Optional[str] = Field(
|
||||
None, description='Name of the relevant git branch'
|
||||
)
|
||||
comfy_run_flags: Optional[str] = Field(
|
||||
None, description='The comfy run flags. E.g. `--low-vram`'
|
||||
)
|
||||
commit_hash: Optional[str] = Field(None, description='The hash of the commit')
|
||||
commit_id: Optional[str] = Field(None, description='The ID of the commit')
|
||||
commit_message: Optional[str] = Field(None, description='The message of the commit')
|
||||
commit_time: Optional[int] = Field(
|
||||
None, description='The Unix timestamp when the commit was made'
|
||||
)
|
||||
cuda_version: Optional[str] = Field(None, description='CUDA version used')
|
||||
end_time: Optional[int] = Field(
|
||||
None, description='The end time of the job as a Unix timestamp.'
|
||||
)
|
||||
git_repo: Optional[str] = Field(None, description='The repository name')
|
||||
id: Optional[UUID] = Field(None, description='Unique identifier for the job result')
|
||||
job_trigger_user: Optional[str] = Field(
|
||||
None, description='The user who triggered the job.'
|
||||
)
|
||||
machine_stats: Optional[MachineStats] = None
|
||||
operating_system: Optional[str] = Field(None, description='Operating system used')
|
||||
peak_vram: Optional[int] = Field(None, description='The peak VRAM used by the job')
|
||||
pr_number: Optional[str] = Field(None, description='The pull request number')
|
||||
python_version: Optional[str] = Field(None, description='PyTorch version used')
|
||||
pytorch_version: Optional[str] = Field(None, description='PyTorch version used')
|
||||
start_time: Optional[int] = Field(
|
||||
None, description='The start time of the job as a Unix timestamp.'
|
||||
)
|
||||
status: Optional[WorkflowRunStatus] = None
|
||||
storage_file: Optional[StorageFile] = None
|
||||
workflow_name: Optional[str] = Field(None, description='Name of the workflow')
|
||||
|
||||
|
||||
class NodeVersion(BaseModel):
|
||||
changelog: Optional[str] = Field(
|
||||
None, description='Summary of changes made in this version'
|
||||
)
|
||||
comfy_node_extract_status: Optional[str] = Field(
|
||||
None, description='The status of comfy node extraction process.'
|
||||
)
|
||||
createdAt: Optional[datetime] = Field(
|
||||
None, description='The date and time the version was created.'
|
||||
)
|
||||
dependencies: Optional[List[str]] = Field(
|
||||
None, description='A list of pip dependencies required by the node.'
|
||||
)
|
||||
deprecated: Optional[bool] = Field(
|
||||
None, description='Indicates if this version is deprecated.'
|
||||
)
|
||||
downloadUrl: Optional[str] = Field(
|
||||
None, description='[Output Only] URL to download this version of the node'
|
||||
)
|
||||
id: Optional[str] = None
|
||||
node_id: Optional[str] = Field(
|
||||
None, description='The unique identifier of the node.'
|
||||
)
|
||||
status: Optional[NodeVersionStatus] = None
|
||||
status_reason: Optional[str] = Field(
|
||||
None, description='The reason for the status change.'
|
||||
)
|
||||
version: Optional[str] = Field(
|
||||
None,
|
||||
description='The version identifier, following semantic versioning. Must be unique for the node.',
|
||||
)
|
||||
|
||||
|
||||
class PublisherMember(BaseModel):
|
||||
id: Optional[str] = Field(
|
||||
None, description='The unique identifier for the publisher member.'
|
||||
)
|
||||
role: Optional[str] = Field(
|
||||
None, description='The role of the user in the publisher.'
|
||||
)
|
||||
user: Optional[PublisherUser] = None
|
||||
|
||||
|
||||
class Publisher(BaseModel):
|
||||
createdAt: Optional[datetime] = Field(
|
||||
None, description='The date and time the publisher was created.'
|
||||
)
|
||||
description: Optional[str] = None
|
||||
id: Optional[str] = Field(
|
||||
None,
|
||||
description="The unique identifier for the publisher. It's akin to a username. Should be lowercase.",
|
||||
)
|
||||
logo: Optional[str] = Field(None, description="URL to the publisher's logo.")
|
||||
members: Optional[List[PublisherMember]] = Field(
|
||||
None, description='A list of members in the publisher.'
|
||||
)
|
||||
name: Optional[str] = None
|
||||
source_code_repo: Optional[str] = None
|
||||
status: Optional[PublisherStatus] = None
|
||||
support: Optional[str] = None
|
||||
website: Optional[str] = None
|
||||
|
||||
|
||||
class Node(BaseModel):
|
||||
author: Optional[str] = None
|
||||
category: Optional[str] = Field(None, description='The category of the node.')
|
||||
description: Optional[str] = None
|
||||
downloads: Optional[int] = Field(
|
||||
None, description='The number of downloads of the node.'
|
||||
)
|
||||
icon: Optional[str] = Field(None, description="URL to the node's icon.")
|
||||
id: Optional[str] = Field(None, description='The unique identifier of the node.')
|
||||
latest_version: Optional[NodeVersion] = None
|
||||
license: Optional[str] = Field(
|
||||
None, description="The path to the LICENSE file in the node's repository."
|
||||
)
|
||||
name: Optional[str] = Field(None, description='The display name of the node.')
|
||||
publisher: Optional[Publisher] = None
|
||||
rating: Optional[float] = Field(None, description='The average rating of the node.')
|
||||
repository: Optional[str] = Field(None, description="URL to the node's repository.")
|
||||
status: Optional[NodeStatus] = None
|
||||
status_detail: Optional[str] = Field(
|
||||
None, description='The status detail of the node.'
|
||||
)
|
||||
tags: Optional[List[str]] = None
|
||||
translations: Optional[Dict[str, Dict[str, Any]]] = None
|
||||
@ -1,279 +0,0 @@
|
||||
# Add API base URL at the top of the file
|
||||
API_BASE = "https://stagingapi.comfy.org"
|
||||
|
||||
from inspect import cleandoc
|
||||
from comfy.comfy_types.node_typing import ComfyNodeABC, InputTypeDict, IO
|
||||
|
||||
def check_auth_token(auth_token):
|
||||
"""Verify that an auth token is present."""
|
||||
if auth_token is None:
|
||||
raise Exception("Please login first to use this node.")
|
||||
return auth_token
|
||||
|
||||
class IdeogramTextToImage(ComfyNodeABC):
|
||||
"""
|
||||
Generates images synchronously based on a given prompt and optional parameters.
|
||||
|
||||
Images links are available for a limited period of time; if you would like to keep the image, you must download it.
|
||||
"""
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls) -> InputTypeDict:
|
||||
"""
|
||||
Return a dictionary which contains config for all input fields.
|
||||
Some types (string): "MODEL", "VAE", "CLIP", "CONDITIONING", "LATENT", "IMAGE", "INT", "STRING", "FLOAT".
|
||||
Input types "INT", "STRING" or "FLOAT" are special values for fields on the node.
|
||||
The type can be a list for selection.
|
||||
|
||||
Returns: `dict`:
|
||||
- Key input_fields_group (`string`): Can be either required, hidden or optional. A node class must have property `required`
|
||||
- Value input_fields (`dict`): Contains input fields config:
|
||||
* Key field_name (`string`): Name of a entry-point method's argument
|
||||
* Value field_config (`tuple`):
|
||||
+ First value is a string indicate the type of field or a list for selection.
|
||||
+ Secound value is a config for type "INT", "STRING" or "FLOAT".
|
||||
"""
|
||||
return {
|
||||
"required": {
|
||||
"prompt": (IO.STRING, {
|
||||
"multiline": True,
|
||||
"default": "",
|
||||
"tooltip": "Prompt for the image generation",
|
||||
}),
|
||||
"model": (IO.COMBO, { "options": ["V_2", "V_2_TURBO", "V_1", "V_1_TURBO"], "default": "V_2", "tooltip": "Model to use for image generation"}),
|
||||
},
|
||||
"optional": {
|
||||
"aspect_ratio": (IO.COMBO, { "options": ["ASPECT_1_1", "ASPECT_4_3", "ASPECT_3_4", "ASPECT_16_9", "ASPECT_9_16", "ASPECT_2_1", "ASPECT_1_2", "ASPECT_3_2", "ASPECT_2_3", "ASPECT_4_5", "ASPECT_5_4"], "default": "ASPECT_1_1", "tooltip": "The aspect ratio for image generation. Cannot be used with resolution"
|
||||
}),
|
||||
"resolution": (IO.COMBO, { "options": ["1024x1024", "1024x1792", "1792x1024"],
|
||||
"default": "1024x1024",
|
||||
"tooltip": "The resolution for image generation (V2 only). Cannot be used with aspect_ratio"
|
||||
}),
|
||||
"magic_prompt_option": (IO.COMBO, { "options": ["AUTO", "ON", "OFF"],
|
||||
"default": "AUTO",
|
||||
"tooltip": "Determine if MagicPrompt should be used in generation"
|
||||
}),
|
||||
"seed": (IO.INT, {
|
||||
"default": 0,
|
||||
"min": 0,
|
||||
"max": 2147483647,
|
||||
"step": 1,
|
||||
"display": "number"
|
||||
}),
|
||||
"style_type": (IO.COMBO, { "options": ["NONE", "ANIME", "CINEMATIC", "CREATIVE", "DIGITAL_ART", "PHOTOGRAPHIC"],
|
||||
"default": "NONE",
|
||||
"tooltip": "Style type for generation (V2+ only)"
|
||||
}),
|
||||
"negative_prompt": (IO.STRING, {
|
||||
"multiline": True,
|
||||
"default": "",
|
||||
"tooltip": "Description of what to exclude from the image (V1/V2 only)"
|
||||
}),
|
||||
"num_images": (IO.INT, {
|
||||
"default": 1,
|
||||
"min": 1,
|
||||
"max": 8,
|
||||
"step": 1,
|
||||
"display": "number"
|
||||
}),
|
||||
"color_palette": (IO.STRING, {
|
||||
"multiline": False,
|
||||
"default": "",
|
||||
"tooltip": "Color palette preset name or hex colors with weights (V2/V2_TURBO only)"
|
||||
}),
|
||||
},
|
||||
"hidden": {
|
||||
"auth_token": "AUTH_TOKEN_COMFY_ORG"
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = (IO.IMAGE,)
|
||||
DESCRIPTION = cleandoc(__doc__ or "") # Handle potential None value
|
||||
FUNCTION = "api_call"
|
||||
API_NODE = True
|
||||
CATEGORY = "Example"
|
||||
|
||||
def api_call(self, prompt, model, aspect_ratio=None, resolution=None,
|
||||
magic_prompt_option="AUTO", seed=0, style_type="NONE",
|
||||
negative_prompt="", num_images=1, color_palette="", auth_token=None):
|
||||
import requests
|
||||
import torch
|
||||
from PIL import Image
|
||||
import io
|
||||
import numpy as np
|
||||
|
||||
check_auth_token(auth_token)
|
||||
|
||||
# Build payload with all available parameters
|
||||
payload = {
|
||||
"image_request": {
|
||||
"prompt": prompt,
|
||||
"model": model,
|
||||
"num_images": num_images,
|
||||
"seed": seed,
|
||||
}
|
||||
}
|
||||
|
||||
# Make API request
|
||||
headers = {
|
||||
"Authorization": f"Bearer {auth_token}",
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
|
||||
response = requests.post(
|
||||
f"{API_BASE}/proxy/ideogram/generate",
|
||||
headers=headers,
|
||||
json=payload
|
||||
)
|
||||
|
||||
if response.status_code != 200:
|
||||
raise Exception(f"API request failed: {response.text}")
|
||||
|
||||
# Parse response
|
||||
response_data = response.json()
|
||||
|
||||
# Get the image URL from the response
|
||||
image_url = response_data["data"][0]["url"]
|
||||
|
||||
img_response = requests.get(image_url)
|
||||
if img_response.status_code != 200:
|
||||
raise Exception("Failed to download the image")
|
||||
|
||||
img = Image.open(io.BytesIO(img_response.content))
|
||||
img = img.convert("RGB") # Ensure RGB format
|
||||
|
||||
# Convert to numpy array, normalize to float32 between 0 and 1
|
||||
img_array = np.array(img).astype(np.float32) / 255.0
|
||||
|
||||
# Convert to torch tensor and add batch dimension
|
||||
img_tensor = torch.from_numpy(img_array)[None,]
|
||||
|
||||
return (img_tensor,)
|
||||
|
||||
"""
|
||||
The node will always be re executed if any of the inputs change but
|
||||
this method can be used to force the node to execute again even when the inputs don't change.
|
||||
You can make this node return a number or a string. This value will be compared to the one returned the last time the node was
|
||||
executed, if it is different the node will be executed again.
|
||||
This method is used in the core repo for the LoadImage node where they return the image hash as a string, if the image hash
|
||||
changes between executions the LoadImage node is executed again.
|
||||
"""
|
||||
#@classmethod
|
||||
#def IS_CHANGED(s, image, string_field, int_field, float_field, print_to_screen):
|
||||
# return ""
|
||||
|
||||
|
||||
class RunwayVideoNode:
|
||||
"""
|
||||
Generates videos synchronously based on a given image, prompt, and optional parameters using Runway's API.
|
||||
"""
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {
|
||||
"required": {
|
||||
"prompt_image": ("IMAGE",), # Will need to handle image URL conversion
|
||||
"prompt_text": ("STRING", {
|
||||
"multiline": True,
|
||||
"default": "",
|
||||
"tooltip": "Text prompt to guide the video generation"
|
||||
}),
|
||||
},
|
||||
"optional": {
|
||||
"seed": ("INT", {
|
||||
"default": 0,
|
||||
"min": 0,
|
||||
"max": 4294967295,
|
||||
"step": 1,
|
||||
"display": "number"
|
||||
}),
|
||||
"model": (["gen3a_turbo"], {
|
||||
"default": "gen3a_turbo",
|
||||
"tooltip": "Model to use for video generation"
|
||||
}),
|
||||
"duration": ("FLOAT", {
|
||||
"default": 5.0,
|
||||
"min": 1.0,
|
||||
"max": 10.0,
|
||||
"step": 0.1,
|
||||
"display": "number",
|
||||
"tooltip": "Duration of the generated video in seconds"
|
||||
}),
|
||||
"ratio": (["1280:768", "768:1280"], {
|
||||
"default": "1280:768",
|
||||
"tooltip": "Aspect ratio of the output video"
|
||||
}),
|
||||
"watermark": ("BOOLEAN", {
|
||||
"default": False,
|
||||
"tooltip": "Whether to include watermark in the output"
|
||||
}),
|
||||
},
|
||||
"hidden": {
|
||||
"auth_token": "AUTH_TOKEN_COMFY_ORG"
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("VIDEO",)
|
||||
DESCRIPTION = "Generates videos from images using Runway's API"
|
||||
FUNCTION = "generate_video"
|
||||
CATEGORY = "video"
|
||||
API_NODE = True
|
||||
|
||||
def generate_video(self, prompt_image, prompt_text, seed=0, model="gen3a_turbo",
|
||||
duration=5.0, ratio="1280:768", watermark=False, auth_token=None):
|
||||
import requests
|
||||
check_auth_token(auth_token)
|
||||
# Convert torch tensor image to URL (you'll need to implement this part)
|
||||
# This is a placeholder - you'll need to either save the image temporarily
|
||||
# or upload it to a service that can host it
|
||||
image_url = "http://example.com" # Placeholder
|
||||
|
||||
# Build payload
|
||||
payload = {
|
||||
"promptImage": image_url,
|
||||
"promptText": prompt_text,
|
||||
"seed": seed,
|
||||
"model": model,
|
||||
"watermark": watermark,
|
||||
"duration": duration,
|
||||
"ratio": ratio
|
||||
}
|
||||
|
||||
# Make API request
|
||||
headers = {
|
||||
"Authorization": f"Bearer {auth_token}",
|
||||
"Content-Type": "application/json",
|
||||
}
|
||||
|
||||
response = requests.post(
|
||||
f"{API_BASE}/proxy/runway/image_to_video",
|
||||
headers=headers,
|
||||
json=payload
|
||||
)
|
||||
|
||||
if response.status_code != 200:
|
||||
raise Exception(f"API request failed: {response.text}")
|
||||
|
||||
# Parse response
|
||||
# response_data = response.json()
|
||||
|
||||
# Note: You'll need to implement the actual video handling here
|
||||
# This is a placeholder return
|
||||
return (None,)
|
||||
|
||||
# A dictionary that contains all nodes you want to export with their names
|
||||
# NOTE: names should be globally unique
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
"IdeogramTextToImage": IdeogramTextToImage,
|
||||
"RunwayVideoNode": RunwayVideoNode
|
||||
}
|
||||
|
||||
# A dictionary that contains the friendly/humanly readable titles for the nodes
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"IdeogramTextToImage": "Ideogram Text to Image",
|
||||
"RunwayVideoNode": "Runway Video Generator"
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user