| | from typing import Union |
| | import logging |
| | import torch |
| |
|
| | from comfy.comfy_types.node_typing import IO |
| | from comfy_api.input_impl.video_types import VideoFromFile |
| | from comfy_api_nodes.apis import ( |
| | MinimaxVideoGenerationRequest, |
| | MinimaxVideoGenerationResponse, |
| | MinimaxFileRetrieveResponse, |
| | MinimaxTaskResultResponse, |
| | SubjectReferenceItem, |
| | Model |
| | ) |
| | from comfy_api_nodes.apis.client import ( |
| | ApiEndpoint, |
| | HttpMethod, |
| | SynchronousOperation, |
| | PollingOperation, |
| | EmptyRequest, |
| | ) |
| | from comfy_api_nodes.apinode_utils import ( |
| | download_url_to_bytesio, |
| | upload_images_to_comfyapi, |
| | validate_string, |
| | ) |
| | from server import PromptServer |
| |
|
| |
|
| | I2V_AVERAGE_DURATION = 114 |
| | T2V_AVERAGE_DURATION = 234 |
| |
|
| | class MinimaxTextToVideoNode: |
| | """ |
| | Generates videos synchronously based on a prompt, and optional parameters using MiniMax's API. |
| | """ |
| |
|
| | AVERAGE_DURATION = T2V_AVERAGE_DURATION |
| |
|
| | @classmethod |
| | def INPUT_TYPES(s): |
| | return { |
| | "required": { |
| | "prompt_text": ( |
| | "STRING", |
| | { |
| | "multiline": True, |
| | "default": "", |
| | "tooltip": "Text prompt to guide the video generation", |
| | }, |
| | ), |
| | "model": ( |
| | [ |
| | "T2V-01", |
| | "T2V-01-Director", |
| | ], |
| | { |
| | "default": "T2V-01", |
| | "tooltip": "Model to use for video generation", |
| | }, |
| | ), |
| | }, |
| | "optional": { |
| | "seed": ( |
| | IO.INT, |
| | { |
| | "default": 0, |
| | "min": 0, |
| | "max": 0xFFFFFFFFFFFFFFFF, |
| | "control_after_generate": True, |
| | "tooltip": "The random seed used for creating the noise.", |
| | }, |
| | ), |
| | }, |
| | "hidden": { |
| | "auth_token": "AUTH_TOKEN_COMFY_ORG", |
| | "comfy_api_key": "API_KEY_COMFY_ORG", |
| | "unique_id": "UNIQUE_ID", |
| | }, |
| | } |
| |
|
| | RETURN_TYPES = ("VIDEO",) |
| | DESCRIPTION = "Generates videos from prompts using MiniMax's API" |
| | FUNCTION = "generate_video" |
| | CATEGORY = "api node/video/MiniMax" |
| | API_NODE = True |
| | OUTPUT_NODE = True |
| |
|
| | def generate_video( |
| | self, |
| | prompt_text, |
| | seed=0, |
| | model="T2V-01", |
| | image: torch.Tensor=None, |
| | subject: torch.Tensor=None, |
| | unique_id: Union[str, None]=None, |
| | **kwargs, |
| | ): |
| | ''' |
| | Function used between MiniMax nodes - supports T2V, I2V, and S2V, based on provided arguments. |
| | ''' |
| | if image is None: |
| | validate_string(prompt_text, field_name="prompt_text") |
| | |
| | image_url = None |
| | if image is not None: |
| | image_url = upload_images_to_comfyapi(image, max_images=1, auth_kwargs=kwargs)[0] |
| |
|
| | |
| | subject_reference = None |
| | if subject is not None: |
| | subject_url = upload_images_to_comfyapi(subject, max_images=1, auth_kwargs=kwargs)[0] |
| | subject_reference = [SubjectReferenceItem(image=subject_url)] |
| |
|
| |
|
| | video_generate_operation = SynchronousOperation( |
| | endpoint=ApiEndpoint( |
| | path="/proxy/minimax/video_generation", |
| | method=HttpMethod.POST, |
| | request_model=MinimaxVideoGenerationRequest, |
| | response_model=MinimaxVideoGenerationResponse, |
| | ), |
| | request=MinimaxVideoGenerationRequest( |
| | model=Model(model), |
| | prompt=prompt_text, |
| | callback_url=None, |
| | first_frame_image=image_url, |
| | subject_reference=subject_reference, |
| | prompt_optimizer=None, |
| | ), |
| | auth_kwargs=kwargs, |
| | ) |
| | response = video_generate_operation.execute() |
| |
|
| | task_id = response.task_id |
| | if not task_id: |
| | raise Exception(f"MiniMax generation failed: {response.base_resp}") |
| |
|
| | video_generate_operation = PollingOperation( |
| | poll_endpoint=ApiEndpoint( |
| | path="/proxy/minimax/query/video_generation", |
| | method=HttpMethod.GET, |
| | request_model=EmptyRequest, |
| | response_model=MinimaxTaskResultResponse, |
| | query_params={"task_id": task_id}, |
| | ), |
| | completed_statuses=["Success"], |
| | failed_statuses=["Fail"], |
| | status_extractor=lambda x: x.status.value, |
| | estimated_duration=self.AVERAGE_DURATION, |
| | node_id=unique_id, |
| | auth_kwargs=kwargs, |
| | ) |
| | task_result = video_generate_operation.execute() |
| |
|
| | file_id = task_result.file_id |
| | if file_id is None: |
| | raise Exception("Request was not successful. Missing file ID.") |
| | file_retrieve_operation = SynchronousOperation( |
| | endpoint=ApiEndpoint( |
| | path="/proxy/minimax/files/retrieve", |
| | method=HttpMethod.GET, |
| | request_model=EmptyRequest, |
| | response_model=MinimaxFileRetrieveResponse, |
| | query_params={"file_id": int(file_id)}, |
| | ), |
| | request=EmptyRequest(), |
| | auth_kwargs=kwargs, |
| | ) |
| | file_result = file_retrieve_operation.execute() |
| |
|
| | file_url = file_result.file.download_url |
| | if file_url is None: |
| | raise Exception( |
| | f"No video was found in the response. Full response: {file_result.model_dump()}" |
| | ) |
| | logging.info(f"Generated video URL: {file_url}") |
| | if unique_id: |
| | if hasattr(file_result.file, "backup_download_url"): |
| | message = f"Result URL: {file_url}\nBackup URL: {file_result.file.backup_download_url}" |
| | else: |
| | message = f"Result URL: {file_url}" |
| | PromptServer.instance.send_progress_text(message, unique_id) |
| |
|
| | video_io = download_url_to_bytesio(file_url) |
| | if video_io is None: |
| | error_msg = f"Failed to download video from {file_url}" |
| | logging.error(error_msg) |
| | raise Exception(error_msg) |
| | return (VideoFromFile(video_io),) |
| |
|
| |
|
| | class MinimaxImageToVideoNode(MinimaxTextToVideoNode): |
| | """ |
| | Generates videos synchronously based on an image and prompt, and optional parameters using MiniMax's API. |
| | """ |
| |
|
| | AVERAGE_DURATION = I2V_AVERAGE_DURATION |
| |
|
| | @classmethod |
| | def INPUT_TYPES(s): |
| | return { |
| | "required": { |
| | "image": ( |
| | IO.IMAGE, |
| | { |
| | "tooltip": "Image to use as first frame of video generation" |
| | }, |
| | ), |
| | "prompt_text": ( |
| | "STRING", |
| | { |
| | "multiline": True, |
| | "default": "", |
| | "tooltip": "Text prompt to guide the video generation", |
| | }, |
| | ), |
| | "model": ( |
| | [ |
| | "I2V-01-Director", |
| | "I2V-01", |
| | "I2V-01-live", |
| | ], |
| | { |
| | "default": "I2V-01", |
| | "tooltip": "Model to use for video generation", |
| | }, |
| | ), |
| | }, |
| | "optional": { |
| | "seed": ( |
| | IO.INT, |
| | { |
| | "default": 0, |
| | "min": 0, |
| | "max": 0xFFFFFFFFFFFFFFFF, |
| | "control_after_generate": True, |
| | "tooltip": "The random seed used for creating the noise.", |
| | }, |
| | ), |
| | }, |
| | "hidden": { |
| | "auth_token": "AUTH_TOKEN_COMFY_ORG", |
| | "comfy_api_key": "API_KEY_COMFY_ORG", |
| | "unique_id": "UNIQUE_ID", |
| | }, |
| | } |
| |
|
| | RETURN_TYPES = ("VIDEO",) |
| | DESCRIPTION = "Generates videos from an image and prompts using MiniMax's API" |
| | FUNCTION = "generate_video" |
| | CATEGORY = "api node/video/MiniMax" |
| | API_NODE = True |
| | OUTPUT_NODE = True |
| |
|
| |
|
| | class MinimaxSubjectToVideoNode(MinimaxTextToVideoNode): |
| | """ |
| | Generates videos synchronously based on an image and prompt, and optional parameters using MiniMax's API. |
| | """ |
| |
|
| | AVERAGE_DURATION = T2V_AVERAGE_DURATION |
| |
|
| | @classmethod |
| | def INPUT_TYPES(s): |
| | return { |
| | "required": { |
| | "subject": ( |
| | IO.IMAGE, |
| | { |
| | "tooltip": "Image of subject to reference video generation" |
| | }, |
| | ), |
| | "prompt_text": ( |
| | "STRING", |
| | { |
| | "multiline": True, |
| | "default": "", |
| | "tooltip": "Text prompt to guide the video generation", |
| | }, |
| | ), |
| | "model": ( |
| | [ |
| | "S2V-01", |
| | ], |
| | { |
| | "default": "S2V-01", |
| | "tooltip": "Model to use for video generation", |
| | }, |
| | ), |
| | }, |
| | "optional": { |
| | "seed": ( |
| | IO.INT, |
| | { |
| | "default": 0, |
| | "min": 0, |
| | "max": 0xFFFFFFFFFFFFFFFF, |
| | "control_after_generate": True, |
| | "tooltip": "The random seed used for creating the noise.", |
| | }, |
| | ), |
| | }, |
| | "hidden": { |
| | "auth_token": "AUTH_TOKEN_COMFY_ORG", |
| | "comfy_api_key": "API_KEY_COMFY_ORG", |
| | "unique_id": "UNIQUE_ID", |
| | }, |
| | } |
| |
|
| | RETURN_TYPES = ("VIDEO",) |
| | DESCRIPTION = "Generates videos from an image and prompts using MiniMax's API" |
| | FUNCTION = "generate_video" |
| | CATEGORY = "api node/video/MiniMax" |
| | API_NODE = True |
| | OUTPUT_NODE = True |
| |
|
| |
|
| | |
| | |
| | NODE_CLASS_MAPPINGS = { |
| | "MinimaxTextToVideoNode": MinimaxTextToVideoNode, |
| | "MinimaxImageToVideoNode": MinimaxImageToVideoNode, |
| | |
| | } |
| |
|
| | |
| | NODE_DISPLAY_NAME_MAPPINGS = { |
| | "MinimaxTextToVideoNode": "MiniMax Text to Video", |
| | "MinimaxImageToVideoNode": "MiniMax Image to Video", |
| | "MinimaxSubjectToVideoNode": "MiniMax Subject to Video", |
| | } |
| |
|