from typing import Literal 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, Model ) from comfy_api_nodes.apis.client import ( ApiEndpoint, HttpMethod, SynchronousOperation, PollingOperation, EmptyRequest, ) from comfy_api_nodes.nodes_api import ( download_url_to_bytesio, ) import logging import folder_paths class MinimaxTextToVideoNode: """ Generates videos synchronously based on a prompt, and optional parameters using Minimax's API. """ def __init__(self): self.output_dir = folder_paths.get_output_directory() self.type: Literal["output"] = "output" @classmethod def INPUT_TYPES(s): return { "required": { "prompt_text": ( "STRING", { "multiline": True, "default": "", "tooltip": "Text prompt to guide the video generation", }, ), "model": ( [ "T2V-01", "I2V-01-Director", "S2V-01", "I2V-01", "I2V-01-live", ], { "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", }, } 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", auth_token=None, ): 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=None, subject_reference=None, prompt_optimizer=None, ), auth_token=auth_token, ) 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, auth_token=auth_token, ) 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_token=auth_token, ) 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}") 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),) # A dictionary that contains all nodes you want to export with their names # NOTE: names should be globally unique NODE_CLASS_MAPPINGS = { "MinimaxTextToVideoNode": MinimaxTextToVideoNode, } # A dictionary that contains the friendly/humanly readable titles for the nodes NODE_DISPLAY_NAME_MAPPINGS = { "MinimaxTextToVideoNode": "Minimax Text to Video", }