From 723b656af3d5aa9aec20c6de9c36cde705d2dd2f Mon Sep 17 00:00:00 2001 From: Christian Byrne Date: Tue, 29 Apr 2025 12:44:23 -0700 Subject: [PATCH] Add Kling Nodes (#12) --- comfy_api_nodes/apis/__init__.py | 2286 ++++++++---------------------- comfy_api_nodes/apis/client.py | 1 - comfy_api_nodes/nodes_kling.py | 479 +++++++ nodes.py | 3 +- 4 files changed, 1070 insertions(+), 1699 deletions(-) create mode 100644 comfy_api_nodes/nodes_kling.py diff --git a/comfy_api_nodes/apis/__init__.py b/comfy_api_nodes/apis/__init__.py index 562a7addb..7c41e936d 100644 --- a/comfy_api_nodes/apis/__init__.py +++ b/comfy_api_nodes/apis/__init__.py @@ -1,123 +1,14 @@ # generated by datamodel-codegen: -# filename: https://stagingapi.comfy.org/openapi -# timestamp: 2025-04-29T03:13:19+00:00 +# filename: filtered-openapi.yaml +# timestamp: 2025-04-29T19:38:18+00:00 from __future__ import annotations from datetime import datetime from enum import Enum -from typing import Any, Dict, List, Literal, Optional, Union -from uuid import UUID +from typing import Any, Dict, List, Optional, Union -from pydantic import AnyUrl, BaseModel, Field, RootModel, confloat, conint, constr - - -class BFLFluxProGenerateRequest(BaseModel): - guidance_scale: Optional[confloat(ge=1.0, le=20.0)] = Field( - None, description='The guidance scale for generation.' - ) - height: conint(ge=64, le=2048) = Field( - ..., description='The height of the image to generate.' - ) - negative_prompt: Optional[str] = Field( - None, description='The negative prompt for image generation.' - ) - num_images: Optional[conint(ge=1, le=4)] = Field( - None, description='The number of images to generate.' - ) - num_inference_steps: Optional[conint(ge=1, le=100)] = Field( - None, description='The number of inference steps.' - ) - prompt: str = Field(..., description='The text prompt for image generation.') - seed: Optional[int] = Field(None, description='The seed value for reproducibility.') - width: conint(ge=64, le=2048) = Field( - ..., description='The width of the image to generate.' - ) - - -class BFLFluxProGenerateResponse(BaseModel): - id: str = Field(..., description='The unique identifier for the generation task.') - polling_url: str = Field(..., description='URL to poll for the generation result.') - - -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 CustomerStorageResourceResponse(BaseModel): - download_url: Optional[str] = Field( - None, - description='The signed URL to use for downloading the file from the specified path', - ) - existing_file: Optional[bool] = Field( - None, description='Whether an existing file with the same hash was found' - ) - expires_at: Optional[datetime] = Field( - None, description='When the signed URL will expire' - ) - upload_url: Optional[str] = Field( - None, - description='The signed URL to use for uploading the file to the specified path', - ) - - -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.' - ) +from pydantic import AnyUrl, BaseModel, Field, RootModel class ErrorResponse(BaseModel): @@ -125,53 +16,44 @@ class ErrorResponse(BaseModel): 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): + prompt: str = Field( + ..., description='Required. The prompt to use to generate the image.' + ) 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.' - ) + model: str = Field(..., description="The model used (e.g., 'V_2', 'V_2A_TURBO')") magic_prompt_option: Optional[str] = Field( None, description="Optional. MagicPrompt usage ('AUTO', 'ON', 'OFF')." ) - model: str = Field(..., description="The model used (e.g., 'V_2', 'V_2A_TURBO')") + seed: Optional[int] = Field( + None, + description='Optional. A number between 0 and 2147483647.', + ge=0, + le=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.", + ) 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.' + num_images: Optional[int] = Field( + 1, + description='Optional. Number of images to generate (1-8). Defaults to 1.', + ge=1, + le=8, ) 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.", + color_palette: Optional[Dict[str, Any]] = Field( + None, description='Optional. Color palette object. Only for V_2, V_2_TURBO.' ) @@ -182,23 +64,23 @@ class IdeogramGenerateRequest(BaseModel): 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')." ) + is_image_safe: Optional[bool] = Field( + None, description='Indicates whether the image is considered safe.' + ) seed: Optional[int] = Field( None, description='The seed value used for this generation.' ) + url: Optional[str] = Field(None, description='URL to the generated image.') 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): @@ -210,9 +92,9 @@ class IdeogramGenerateResponse(BaseModel): ) -class Duration(str, Enum): - field_5 = '5' - field_10 = '10' +class ModelName(str, Enum): + kling_v1 = 'kling-v1' + kling_v1_6 = 'kling-v1-6' class Mode(str, Enum): @@ -220,77 +102,6 @@ class Mode(str, Enum): pro = 'pro' -class ModelName(str, Enum): - kling_v1 = 'kling-v1' - kling_v1_5 = 'kling-v1-5' - kling_v1_6 = 'kling-v1-6' - - -class KlingDualCharacterEffectInput(BaseModel): - duration: Duration = Field( - ..., - description='Video Length in seconds. Both 5 and 10-second videos are supported.', - ) - images: List[str] = Field( - ..., - description='Reference Image Group. Must contain exactly 2 images. First image will be positioned on left side, second on right side of the composite. Each image follows the same requirements as single image effects.', - max_length=2, - min_length=2, - ) - mode: Optional[Mode] = Field( - 'std', - description='Video generation mode. std (Standard Mode) is cost-effective, pro (Professional Mode) generates videos with longer duration and higher quality.', - ) - model_name: Optional[ModelName] = Field( - 'kling-v1', - description='Model Name. Can be kling-v1, kling-v1-5, or kling-v1-6.', - ) - - -class KlingErrorResponse(BaseModel): - code: int = Field( - ..., - description='- 1000: Authentication failed\n- 1001: Authorization is empty\n- 1002: Authorization is invalid\n- 1003: Authorization is not yet valid\n- 1004: Authorization has expired\n- 1100: Account exception\n- 1101: Account in arrears (postpaid scenario)\n- 1102: Resource pack depleted or expired (prepaid scenario)\n- 1103: Unauthorized access to requested resource\n- 1200: Invalid request parameters\n- 1201: Invalid parameters\n- 1202: Invalid request method\n- 1203: Requested resource does not exist\n- 1300: Trigger platform strategy\n- 1301: Trigger content security policy\n- 1302: API request too frequent\n- 1303: Concurrency/QPS exceeds limit\n- 1304: Trigger IP whitelist policy\n- 5000: Internal server error\n- 5001: Service temporarily unavailable\n- 5002: Server internal timeout\n', - ) - message: str = Field(..., description='Human-readable error message') - request_id: str = Field( - ..., description='Request ID for tracking and troubleshooting' - ) - - -class AspectRatio(str, Enum): - field_16_9 = '16:9' - field_9_16 = '9:16' - field_1_1 = '1:1' - - -class Config(BaseModel): - horizontal: Optional[confloat(ge=-10.0, le=10.0)] = Field( - None, - description="Controls camera's movement along horizontal axis (x-axis). Negative indicates left, positive indicates right.", - ) - pan: Optional[confloat(ge=-10.0, le=10.0)] = Field( - None, - description="Controls camera's rotation in vertical plane (x-axis). Negative indicates downward rotation, positive indicates upward rotation.", - ) - roll: Optional[confloat(ge=-10.0, le=10.0)] = Field( - None, - description="Controls camera's rolling amount (z-axis). Negative indicates counterclockwise, positive indicates clockwise.", - ) - tilt: Optional[confloat(ge=-10.0, le=10.0)] = Field( - None, - description="Controls camera's rotation in horizontal plane (y-axis). Negative indicates left rotation, positive indicates right rotation.", - ) - vertical: Optional[confloat(ge=-10.0, le=10.0)] = Field( - None, - description="Controls camera's movement along vertical axis (y-axis). Negative indicates downward, positive indicates upward.", - ) - zoom: Optional[confloat(ge=-10.0, le=10.0)] = Field( - None, - description="Controls change in camera's focal length. Negative indicates narrower field of view, positive indicates wider field of view.", - ) - - class Type(str, Enum): simple = 'simple' down_back = 'down_back' @@ -299,12 +110,93 @@ class Type(str, Enum): left_turn_forward = 'left_turn_forward' +class Config(BaseModel): + horizontal: Optional[float] = Field(None, ge=-10.0, le=10.0) + vertical: Optional[float] = Field(None, ge=-10.0, le=10.0) + pan: Optional[float] = Field(None, ge=-10.0, le=10.0) + tilt: Optional[float] = Field(None, ge=-10.0, le=10.0) + roll: Optional[float] = Field(None, ge=-10.0, le=10.0) + zoom: Optional[float] = Field(None, ge=-10.0, le=10.0) + + class CameraControl(BaseModel): + type: Optional[Type] = Field(None, description='Predefined camera movements type') config: Optional[Config] = None - type: Optional[Type] = Field( - None, - description='Predefined camera movements type. simple: Customizable camera movement. down_back: Camera descends and moves backward. forward_up: Camera moves forward and tilts up. right_turn_forward: Rotate right and move forward. left_turn_forward: Rotate left and move forward.', + + +class AspectRatio(str, Enum): + field_16_9 = '16:9' + field_9_16 = '9:16' + field_1_1 = '1:1' + + +class Duration(str, Enum): + field_5 = '5' + field_10 = '10' + + +class KlingText2VideoRequest(BaseModel): + model_name: Optional[ModelName] = Field('kling-v1', description='Model Name') + prompt: Optional[str] = Field( + None, description='Positive text prompt', max_length=2500 ) + negative_prompt: Optional[str] = Field( + None, description='Negative text prompt', max_length=2500 + ) + cfg_scale: Optional[float] = Field( + 0.5, description='Flexibility in video generation', ge=0.0, le=1.0 + ) + mode: Optional[Mode] = Field('std', description='Video generation mode') + camera_control: Optional[CameraControl] = None + aspect_ratio: Optional[AspectRatio] = '16:9' + duration: Optional[Duration] = '5' + callback_url: Optional[AnyUrl] = Field( + None, description='The callback notification address' + ) + external_task_id: Optional[str] = Field(None, description='Customized Task ID') + + +class TaskStatus(str, Enum): + submitted = 'submitted' + processing = 'processing' + succeed = 'succeed' + failed = 'failed' + + +class TaskInfo(BaseModel): + external_task_id: Optional[str] = None + + +class Video(BaseModel): + id: Optional[str] = Field(None, description='Generated video ID') + url: Optional[AnyUrl] = Field(None, description='URL for generated video') + duration: Optional[str] = Field(None, description='Total video duration') + + +class TaskResult(BaseModel): + videos: Optional[List[Video]] = None + + +class Data(BaseModel): + task_id: Optional[str] = Field(None, description='Task ID') + task_status: Optional[TaskStatus] = None + task_info: Optional[TaskInfo] = None + created_at: Optional[int] = Field(None, description='Task creation time') + updated_at: Optional[int] = Field(None, description='Task update time') + task_result: Optional[TaskResult] = None + + +class KlingText2VideoResponse(BaseModel): + code: Optional[int] = Field(None, description='Error code') + message: Optional[str] = Field(None, description='Error message') + request_id: Optional[str] = Field(None, description='Request ID') + data: Optional[Data] = None + + +class ModelName1(str, Enum): + kling_v1 = 'kling-v1' + kling_v1_5 = 'kling-v1-5' + kling_v1_6 = 'kling-v1-6' class Trajectory(BaseModel): @@ -326,761 +218,118 @@ class DynamicMask(BaseModel): trajectories: Optional[List[Trajectory]] = None +class Config1(BaseModel): + horizontal: Optional[float] = Field( + None, + description="Controls camera's movement along horizontal axis (x-axis). Negative indicates left, positive indicates right.", + ge=-10.0, + le=10.0, + ) + vertical: Optional[float] = Field( + None, + description="Controls camera's movement along vertical axis (y-axis). Negative indicates downward, positive indicates upward.", + ge=-10.0, + le=10.0, + ) + pan: Optional[float] = Field( + None, + description="Controls camera's rotation in vertical plane (x-axis). Negative indicates downward rotation, positive indicates upward rotation.", + ge=-10.0, + le=10.0, + ) + tilt: Optional[float] = Field( + None, + description="Controls camera's rotation in horizontal plane (y-axis). Negative indicates left rotation, positive indicates right rotation.", + ge=-10.0, + le=10.0, + ) + roll: Optional[float] = Field( + None, + description="Controls camera's rolling amount (z-axis). Negative indicates counterclockwise, positive indicates clockwise.", + ge=-10.0, + le=10.0, + ) + zoom: Optional[float] = Field( + None, + description="Controls change in camera's focal length. Negative indicates narrower field of view, positive indicates wider field of view.", + ge=-10.0, + le=10.0, + ) + + +class CameraControl1(BaseModel): + type: Optional[Type] = Field( + None, + description='Predefined camera movements type. simple: Customizable camera movement. down_back: Camera descends and moves backward. forward_up: Camera moves forward and tilts up. right_turn_forward: Rotate right and move forward. left_turn_forward: Rotate left and move forward.', + ) + config: Optional[Config1] = None + + class KlingImage2VideoRequest(BaseModel): - aspect_ratio: Optional[AspectRatio] = '16:9' - callback_url: Optional[AnyUrl] = Field( - None, - description='The callback notification address. Server will notify when the task status changes.', - ) - camera_control: Optional[CameraControl] = None - cfg_scale: Optional[confloat(ge=0.0, le=1.0)] = Field( - 0.5, - description="Flexibility in video generation. The higher the value, the lower the model's degree of flexibility, and the stronger the relevance to the user's prompt.", - ) - duration: Optional[Duration] = Field(5, description='Video length in seconds') - dynamic_masks: Optional[List[DynamicMask]] = Field( - None, - description='Dynamic Brush Configuration List (up to 6 groups). For 5-second videos, trajectory length must not exceed 77 coordinates.', - ) - external_task_id: Optional[str] = Field( - None, - description='Customized Task ID. Must be unique within a single user account.', - ) - image: Optional[AnyUrl] = Field( + model_name: Optional[ModelName1] = Field('kling-v1', description='Model Name') + image: Optional[str] = Field( None, description='Reference Image - URL or Base64 encoded string, cannot exceed 10MB, resolution not less than 300*300px, aspect ratio between 1:2.5 ~ 2.5:1. Base64 should not include data:image prefix.', ) - image_tail: Optional[AnyUrl] = Field( + image_tail: Optional[str] = Field( None, description='Reference Image - End frame control. URL or Base64 encoded string, cannot exceed 10MB, resolution not less than 300*300px. Base64 should not include data:image prefix.', ) + prompt: Optional[str] = Field( + None, description='Positive text prompt', max_length=2500 + ) + negative_prompt: Optional[str] = Field( + None, description='Negative text prompt', max_length=2500 + ) + cfg_scale: Optional[float] = Field( + 0.5, + description="Flexibility in video generation. The higher the value, the lower the model's degree of flexibility, and the stronger the relevance to the user's prompt.", + ge=0.0, + le=1.0, + ) mode: Optional[Mode] = Field( 'std', description='Video generation mode. std: Standard Mode, which is cost-effective. pro: Professional Mode, generates videos with longer duration but higher quality output.', ) - model_name: Optional[ModelName] = Field('kling-v1', description='Model Name') - negative_prompt: Optional[constr(max_length=2500)] = Field( - None, description='Negative text prompt' - ) - prompt: Optional[constr(max_length=2500)] = Field( - None, description='Positive text prompt' - ) static_mask: Optional[AnyUrl] = Field( None, description='Static Brush Application Area (Mask image created by users using the motion brush). The aspect ratio must match the input image.', ) - - -class TaskInfo(BaseModel): - external_task_id: Optional[str] = None - - -class Video(BaseModel): - duration: Optional[str] = Field(None, description='Total video duration') - id: Optional[str] = Field(None, description='Generated video ID') - url: Optional[AnyUrl] = Field(None, description='URL for generated video') - - -class TaskResult(BaseModel): - videos: Optional[List[Video]] = None - - -class TaskStatus(str, Enum): - submitted = 'submitted' - processing = 'processing' - succeed = 'succeed' - failed = 'failed' - - -class Data(BaseModel): - created_at: Optional[int] = Field(None, description='Task creation time') - task_id: Optional[str] = Field(None, description='Task ID') - task_info: Optional[TaskInfo] = None - task_result: Optional[TaskResult] = None - task_status: Optional[TaskStatus] = None - updated_at: Optional[int] = Field(None, description='Task update time') - - -class KlingImage2VideoResponse(BaseModel): - code: Optional[int] = Field(None, description='Error code') - data: Optional[Data] = None - message: Optional[str] = Field(None, description='Error message') - request_id: Optional[str] = Field(None, description='Request ID') - - -class AspectRatio1(str, Enum): - field_16_9 = '16:9' - field_9_16 = '9:16' - field_1_1 = '1:1' - field_4_3 = '4:3' - field_3_4 = '3:4' - field_3_2 = '3:2' - field_2_3 = '2:3' - field_21_9 = '21:9' - - -class ImageReference(str, Enum): - subject = 'subject' - face = 'face' - - -class ModelName2(str, Enum): - kling_v1 = 'kling-v1' - kling_v1_5 = 'kling-v1-5' - - -class KlingImageGenerationsRequest(BaseModel): - aspect_ratio: Optional[AspectRatio1] = Field( - '16:9', description='Aspect ratio of the generated images' - ) - callback_url: Optional[AnyUrl] = Field( - None, description='The callback notification address' - ) - human_fidelity: Optional[confloat(ge=0.0, le=1.0)] = Field( - 0.45, description='Subject reference similarity' - ) - image: Optional[str] = Field( - None, description='Reference Image - Base64 encoded string or image URL' - ) - image_fidelity: Optional[confloat(ge=0.0, le=1.0)] = Field( - 0.5, description='Reference intensity for user-uploaded images' - ) - image_reference: Optional[ImageReference] = Field( - None, description='Image reference type' - ) - model_name: Optional[ModelName2] = Field('kling-v1', description='Model Name') - n: Optional[conint(ge=1, le=9)] = Field(1, description='Number of generated images') - negative_prompt: Optional[constr(max_length=200)] = Field( - None, description='Negative text prompt' - ) - prompt: constr(max_length=500) = Field(..., description='Positive text prompt') - - -class Image(BaseModel): - index: Optional[int] = Field(None, description='Image Number (0-9)') - url: Optional[AnyUrl] = Field(None, description='URL for generated image') - - -class TaskResult1(BaseModel): - images: Optional[List[Image]] = None - - -class Data1(BaseModel): - created_at: Optional[int] = Field(None, description='Task creation time') - task_id: Optional[str] = Field(None, description='Task ID') - task_result: Optional[TaskResult1] = None - task_status: Optional[TaskStatus] = None - task_status_msg: Optional[str] = Field(None, description='Task status information') - updated_at: Optional[int] = Field(None, description='Task update time') - - -class KlingImageGenerationsResponse(BaseModel): - code: Optional[int] = Field(None, description='Error code') - data: Optional[Data1] = None - message: Optional[str] = Field(None, description='Error message') - request_id: Optional[str] = Field(None, description='Request ID') - - -class AudioType(str, Enum): - file = 'file' - url = 'url' - - -class Mode2(str, Enum): - text2video = 'text2video' - audio2video = 'audio2video' - - -class VoiceLanguage(str, Enum): - zh = 'zh' - en = 'en' - - -class Input(BaseModel): - audio_file: Optional[str] = Field( + dynamic_masks: Optional[List[DynamicMask]] = Field( None, - description='Local Path of Audio File. Supported formats: .mp3/.wav/.m4a/.aac, maximum file size of 5MB. Base64 code.', + description='Dynamic Brush Configuration List (up to 6 groups). For 5-second videos, trajectory length must not exceed 77 coordinates.', ) - audio_type: Optional[AudioType] = Field( - None, - description='Method of Transmitting Audio Files for Lip-Sync. Required when mode is audio2video.', - ) - audio_url: Optional[AnyUrl] = Field( - None, - description='Audio File Download URL. Supported formats: .mp3/.wav/.m4a/.aac, maximum file size of 5MB.', - ) - mode: Mode2 = Field( - ..., - description='Video Generation Mode. text2video: Text-to-video generation mode; audio2video: Audio-to-video generation mode', - ) - text: Optional[str] = Field( - None, - description='Text Content for Lip-Sync Video Generation. Required when mode is text2video. Maximum length is 120 characters.', - ) - video_id: Optional[str] = Field( - None, - description='The ID of the video generated by Kling AI. Only supports 5-second and 10-second videos generated within the last 30 days.', - ) - video_url: Optional[AnyUrl] = Field( - None, - description='Get link for uploaded video. Video files support .mp4/.mov, file size does not exceed 100MB, video length between 2-10s.', - ) - voice_id: Optional[str] = Field( - None, - description='Voice ID. Required when mode is text2video. The system offers a variety of voice options to choose from.', - ) - voice_language: Optional[VoiceLanguage] = Field( - 'zh', description='The voice language corresponds to the Voice ID.' - ) - voice_speed: Optional[confloat(ge=0.8, le=2.0)] = Field( - 1, - description='Speech Rate. Valid range: 0.8~2.0, accurate to one decimal place.', - ) - - -class KlingLipSyncRequest(BaseModel): + camera_control: Optional[CameraControl1] = None + aspect_ratio: Optional[AspectRatio] = '16:9' + duration: Optional[Duration] = Field('5', description='Video length in seconds') callback_url: Optional[AnyUrl] = Field( None, description='The callback notification address. Server will notify when the task status changes.', ) - input: Input - - -class TaskResult2(BaseModel): - videos: Optional[List[Video]] = None - - -class Data2(BaseModel): - created_at: Optional[int] = Field(None, description='Task creation time') - task_id: Optional[str] = Field(None, description='Task ID') - task_info: Optional[TaskInfo] = None - task_result: Optional[TaskResult2] = None - task_status: Optional[TaskStatus] = None - updated_at: Optional[int] = Field(None, description='Task update time') - - -class KlingLipSyncResponse(BaseModel): - code: Optional[int] = Field(None, description='Error code') - data: Optional[Data2] = None - message: Optional[str] = Field(None, description='Error message') - request_id: Optional[str] = Field(None, description='Request ID') - - -class ResourcePackType(str, Enum): - decreasing_total = 'decreasing_total' - constant_period = 'constant_period' - - -class Status(str, Enum): - toBeOnline = 'toBeOnline' - online = 'online' - expired = 'expired' - runOut = 'runOut' - - -class ResourcePackSubscribeInfo(BaseModel): - effective_time: Optional[int] = Field( - None, description='Effective time, Unix timestamp in ms' - ) - invalid_time: Optional[int] = Field( - None, description='Expiration time, Unix timestamp in ms' - ) - purchase_time: Optional[int] = Field( - None, description='Purchase time, Unix timestamp in ms' - ) - remaining_quantity: Optional[float] = Field( - None, description='Remaining quantity (updated with a 12-hour delay)' - ) - resource_pack_id: Optional[str] = Field(None, description='Resource package ID') - resource_pack_name: Optional[str] = Field(None, description='Resource package name') - resource_pack_type: Optional[ResourcePackType] = Field( - None, - description='Resource package type (decreasing_total=decreasing total, constant_period=constant periodicity)', - ) - status: Optional[Status] = Field(None, description='Resource Package Status') - total_quantity: Optional[float] = Field(None, description='Total quantity') - - -class Data3(BaseModel): - code: Optional[int] = Field(None, description='Error code; 0 indicates success') - msg: Optional[str] = Field(None, description='Error information') - resource_pack_subscribe_infos: Optional[List[ResourcePackSubscribeInfo]] = Field( - None, description='Resource package list' - ) - - -class KlingResourcePackageResponse(BaseModel): - code: Optional[int] = Field(None, description='Error code; 0 indicates success') - data: Optional[Data3] = None - message: Optional[str] = Field(None, description='Error information') - request_id: Optional[str] = Field( - None, - description='Request ID, generated by the system, used to track requests and troubleshoot problems', - ) - - -class Duration2(str, Enum): - field_5 = '5' - - -class ModelName3(str, Enum): - kling_v1_6 = 'kling-v1-6' - - -class KlingSingleImageEffectInput(BaseModel): - duration: Duration2 = Field( - ..., description='Video Length in seconds. Only 5-second videos are supported.' - ) - image: str = Field( - ..., - description='Reference Image. URL or Base64 encoded string (without data:image prefix). File size cannot exceed 10MB, resolution not less than 300*300px, aspect ratio between 1:2.5 ~ 2.5:1.', - ) - model_name: ModelName3 = Field( - ..., - description='Model Name. Only kling-v1-6 is supported for single image effects.', - ) - - -class AspectRatio2(str, Enum): - field_16_9 = '16:9' - field_9_16 = '9:16' - field_1_1 = '1:1' - - -class Config1(BaseModel): - horizontal: Optional[confloat(ge=-10.0, le=10.0)] = None - pan: Optional[confloat(ge=-10.0, le=10.0)] = None - roll: Optional[confloat(ge=-10.0, le=10.0)] = None - tilt: Optional[confloat(ge=-10.0, le=10.0)] = None - vertical: Optional[confloat(ge=-10.0, le=10.0)] = None - zoom: Optional[confloat(ge=-10.0, le=10.0)] = None - - -class CameraControl1(BaseModel): - config: Optional[Config1] = None - type: Optional[Type] = Field(None, description='Predefined camera movements type') - - -class Duration3(str, Enum): - field_5 = 5 - field_10 = 10 - - -class Mode3(str, Enum): - std = 'std' - pro = 'pro' - - -class ModelName4(str, Enum): - kling_v1 = 'kling-v1' - kling_v1_6 = 'kling-v1-6' - - -class KlingText2VideoRequest(BaseModel): - aspect_ratio: Optional[AspectRatio2] = '16:9' - callback_url: Optional[AnyUrl] = Field( - None, description='The callback notification address' - ) - camera_control: Optional[CameraControl1] = None - cfg_scale: Optional[confloat(ge=0.0, le=1.0)] = Field( - 0.5, description='Flexibility in video generation' - ) - duration: Optional[Duration3] = 5 - external_task_id: Optional[str] = Field(None, description='Customized Task ID') - mode: Optional[Mode3] = Field('std', description='Video generation mode') - model_name: Optional[ModelName4] = Field('kling-v1', description='Model Name') - negative_prompt: Optional[constr(max_length=2500)] = Field( - None, description='Negative text prompt' - ) - prompt: Optional[constr(max_length=2500)] = Field( - None, description='Positive text prompt' - ) - - -class TaskResult3(BaseModel): - videos: Optional[List[Video]] = None - - -class Data4(BaseModel): - created_at: Optional[int] = Field(None, description='Task creation time') - task_id: Optional[str] = Field(None, description='Task ID') - task_info: Optional[TaskInfo] = None - task_result: Optional[TaskResult3] = None - task_status: Optional[TaskStatus] = None - updated_at: Optional[int] = Field(None, description='Task update time') - - -class KlingText2VideoResponse(BaseModel): - code: Optional[int] = Field(None, description='Error code') - data: Optional[Data4] = None - message: Optional[str] = Field(None, description='Error message') - request_id: Optional[str] = Field(None, description='Request ID') - - -class KlingVideoEffectsInput( - RootModel[Union[KlingSingleImageEffectInput, KlingDualCharacterEffectInput]] -): - root: Union[KlingSingleImageEffectInput, KlingDualCharacterEffectInput] - - -class EffectScene(str, Enum): - bloombloom = 'bloombloom' - dizzydizzy = 'dizzydizzy' - fuzzyfuzzy = 'fuzzyfuzzy' - squish = 'squish' - expansion = 'expansion' - hug = 'hug' - kiss = 'kiss' - heart_gesture = 'heart_gesture' - - -class KlingVideoEffectsRequest(BaseModel): - callback_url: Optional[AnyUrl] = Field( - None, - description='The callback notification address for the result of this task.', - ) - effect_scene: EffectScene = Field( - ..., - description='Scene Name. Single Image Effects (bloombloom, dizzydizzy, fuzzyfuzzy, squish, expansion) or Dual-character Effects (hug, kiss, heart_gesture).', - ) external_task_id: Optional[str] = Field( None, description='Customized Task ID. Must be unique within a single user account.', ) - input: Optional[KlingVideoEffectsInput] = None -class TaskResult4(BaseModel): +class TaskResult1(BaseModel): videos: Optional[List[Video]] = None -class Data5(BaseModel): - created_at: Optional[int] = Field(None, description='Task creation time') +class Data1(BaseModel): task_id: Optional[str] = Field(None, description='Task ID') + task_status: Optional[TaskStatus] = None task_info: Optional[TaskInfo] = None - task_result: Optional[TaskResult4] = None - task_status: Optional[TaskStatus] = None - updated_at: Optional[int] = Field(None, description='Task update time') - - -class KlingVideoEffectsResponse(BaseModel): - code: Optional[int] = Field(None, description='Error code') - data: Optional[Data5] = None - message: Optional[str] = Field(None, description='Error message') - request_id: Optional[str] = Field(None, description='Request ID') - - -class KlingVideoExtendRequest(BaseModel): - callback_url: Optional[AnyUrl] = Field( - None, - description='The callback notification address. Server will notify when the task status changes.', - ) - cfg_scale: Optional[confloat(ge=0.0, le=1.0)] = Field( - 0.5, - description="Flexibility in video generation. The higher the value, the lower the model's flexibility and the stronger the relevance to the user's prompt.", - ) - negative_prompt: Optional[constr(max_length=2500)] = Field( - None, - description='Negative text prompt for elements to avoid in the extended video', - ) - prompt: Optional[constr(max_length=2500)] = Field( - None, description='Positive text prompt for guiding the video extension' - ) - video_id: Optional[str] = Field( - None, - description='The ID of the video to be extended. Supports videos generated by text-to-video, image-to-video, and previous video extension operations. Cannot exceed 3 minutes total duration after extension.', - ) - - -class TaskResult5(BaseModel): - videos: Optional[List[Video]] = None - - -class Data6(BaseModel): created_at: Optional[int] = Field(None, description='Task creation time') - task_id: Optional[str] = Field(None, description='Task ID') - task_info: Optional[TaskInfo] = None - task_result: Optional[TaskResult5] = None - task_status: Optional[TaskStatus] = None updated_at: Optional[int] = Field(None, description='Task update time') + task_result: Optional[TaskResult1] = None -class KlingVideoExtendResponse(BaseModel): +class KlingImage2VideoResponse(BaseModel): code: Optional[int] = Field(None, description='Error code') - data: Optional[Data6] = None message: Optional[str] = Field(None, description='Error message') request_id: Optional[str] = Field(None, description='Request ID') - - -class ModelName5(str, Enum): - kolors_virtual_try_on_v1 = 'kolors-virtual-try-on-v1' - kolors_virtual_try_on_v1_5 = 'kolors-virtual-try-on-v1-5' - - -class KlingVirtualTryOnRequest(BaseModel): - callback_url: Optional[AnyUrl] = Field( - None, description='The callback notification address' - ) - cloth_image: Optional[str] = Field( - None, - description='Reference clothing image - Base64 encoded string or image URL', - ) - human_image: str = Field( - ..., description='Reference human image - Base64 encoded string or image URL' - ) - model_name: Optional[ModelName5] = Field( - 'kolors-virtual-try-on-v1', description='Model Name' - ) - - -class Image1(BaseModel): - index: Optional[int] = Field(None, description='Image Number') - url: Optional[AnyUrl] = Field(None, description='URL for generated image') - - -class TaskResult6(BaseModel): - images: Optional[List[Image1]] = None - - -class Data7(BaseModel): - created_at: Optional[int] = Field(None, description='Task creation time') - task_id: Optional[str] = Field(None, description='Task ID') - task_result: Optional[TaskResult6] = None - task_status: Optional[TaskStatus] = None - task_status_msg: Optional[str] = Field(None, description='Task status information') - updated_at: Optional[int] = Field(None, description='Task update time') - - -class KlingVirtualTryOnResponse(BaseModel): - code: Optional[int] = Field(None, description='Error code') - data: Optional[Data7] = None - message: Optional[str] = Field(None, description='Error message') - request_id: Optional[str] = Field(None, description='Request ID') - - -class LumaAspectRatio(str, Enum): - field_1_1 = '1:1' - field_16_9 = '16:9' - field_9_16 = '9:16' - field_4_3 = '4:3' - field_3_4 = '3:4' - field_21_9 = '21:9' - field_9_21 = '9:21' - - -class LumaAssets(BaseModel): - image: Optional[AnyUrl] = Field(None, description='The URL of the image') - progress_video: Optional[AnyUrl] = Field( - None, description='The URL of the progress video' - ) - video: Optional[AnyUrl] = Field(None, description='The URL of the video') - - -class GenerationType(str, Enum): - add_audio = 'add_audio' - - -class LumaAudioGenerationRequest(BaseModel): - callback_url: Optional[AnyUrl] = Field( - None, description='The callback URL for the audio' - ) - generation_type: Optional[GenerationType] = 'add_audio' - negative_prompt: Optional[str] = Field( - None, description='The negative prompt of the audio' - ) - prompt: Optional[str] = Field(None, description='The prompt of the audio') - - -class LumaError(BaseModel): - detail: Optional[str] = Field(None, description='The error message') - - -class Type2(str, Enum): - generation = 'generation' - - -class LumaGenerationReference(BaseModel): - id: UUID = Field(..., description='The ID of the generation') - type: Literal['generation'] - - -class GenerationType1(str, Enum): - video = 'video' - - -class LumaGenerationType(str, Enum): - video = 'video' - image = 'image' - - -class GenerationType2(str, Enum): - image = 'image' - - -class LumaImageIdentity(BaseModel): - images: Optional[List[AnyUrl]] = Field( - None, description='The URLs of the image identity' - ) - - -class LumaImageModel(str, Enum): - photon_1 = 'photon-1' - photon_flash_1 = 'photon-flash-1' - - -class LumaImageRef(BaseModel): - url: Optional[AnyUrl] = Field(None, description='The URL of the image reference') - weight: Optional[float] = Field( - None, description='The weight of the image reference' - ) - - -class Type3(str, Enum): - image = 'image' - - -class LumaImageReference(BaseModel): - type: Literal['image'] - url: AnyUrl = Field(..., description='The URL of the image') - - -class LumaKeyframe(RootModel[Union[LumaGenerationReference, LumaImageReference]]): - root: Union[LumaGenerationReference, LumaImageReference] = Field( - ..., - description='A keyframe can be either a Generation reference, an Image, or a Video', - discriminator='type', - ) - - -class LumaKeyframes(BaseModel): - frame0: Optional[LumaKeyframe] = None - frame1: Optional[LumaKeyframe] = None - - -class LumaModifyImageRef(BaseModel): - url: Optional[AnyUrl] = Field(None, description='The URL of the image reference') - weight: Optional[float] = Field( - None, description='The weight of the modify image reference' - ) - - -class LumaState(str, Enum): - queued = 'queued' - dreaming = 'dreaming' - completed = 'completed' - failed = 'failed' - - -class GenerationType3(str, Enum): - upscale_video = 'upscale_video' - - -class LumaVideoModel(str, Enum): - ray_2 = 'ray-2' - ray_2_flash = 'ray-2-flash' - - -class LumaVideoModelOutputDuration1(str, Enum): - field_5s = '5s' - field_9s = '9s' - - -class LumaVideoModelOutputDuration( - RootModel[Union[LumaVideoModelOutputDuration1, str]] -): - root: Union[LumaVideoModelOutputDuration1, str] - - -class LumaVideoModelOutputResolution1(str, Enum): - field_540p = '540p' - field_720p = '720p' - field_1080p = '1080p' - field_4k = '4k' - - -class LumaVideoModelOutputResolution( - RootModel[Union[LumaVideoModelOutputResolution1, str]] -): - root: Union[LumaVideoModelOutputResolution1, str] - - -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 Status1(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: Status1 = 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.') + data: Optional[Data1] = None class Model(str, Enum): @@ -1103,251 +352,135 @@ class SubjectReferenceItem(BaseModel): 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( + prompt: Optional[str] = Field( None, description='Description of the video. Should be less than 2000 characters. Supports camera movement instructions in [brackets].', + max_length=2000, ) prompt_optimizer: Optional[bool] = Field( True, description='If true (default), the model will automatically optimize the prompt. Set to false for more precise control.', ) + 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.', + ) 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.', ) + callback_url: Optional[str] = Field( + None, + description='Optional. URL to receive real-time status updates about the video generation task.', + ) + + +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 MinimaxVideoGenerationResponse(BaseModel): - base_resp: MinimaxBaseResponse task_id: str = Field( ..., description='The task ID for the asynchronous video generation task.' ) + base_resp: MinimaxBaseResponse -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.' +class File(BaseModel): + file_id: Optional[int] = Field(None, description='Unique identifier for the file') + 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' ) - deprecated: Optional[bool] = Field( - None, description='Whether the version is deprecated.' + filename: Optional[str] = Field(None, description='The name of the file') + purpose: Optional[str] = Field(None, description='The purpose of using the file') + download_url: Optional[str] = Field( + None, description='The URL to download the video' ) -class Moderation(str, Enum): - low = 'low' - auto = 'auto' +class MinimaxFileRetrieveResponse(BaseModel): + file: File + base_resp: MinimaxBaseResponse -class OutputFormat(str, Enum): - png = 'png' - webp = 'webp' - jpeg = 'jpeg' +class Status(str, Enum): + Queueing = 'Queueing' + Preparing = 'Preparing' + Processing = 'Processing' + Success = 'Success' + Fail = 'Fail' -class OpenAIImageEditRequest(BaseModel): - background: Optional[str] = Field( - None, description='Background transparency', examples=['opaque'] - ) - model: str = Field( - ..., description='The model to use for image editing', examples=['gpt-image-1'] - ) - moderation: Optional[Moderation] = Field( - None, description='Content moderation setting', examples=['auto'] - ) - n: Optional[int] = Field( - None, description='The number of images to generate', examples=[1] - ) - output_compression: Optional[int] = Field( - None, description='Compression level for JPEG or WebP (0-100)', examples=[100] - ) - output_format: Optional[OutputFormat] = Field( - None, description='Format of the output image', examples=['png'] - ) - prompt: str = Field( +class MinimaxTaskResultResponse(BaseModel): + task_id: str = Field(..., description='The task ID being queried.') + status: Status = Field( ..., - description='A text description of the desired edit', - examples=['Give the rocketship rainbow coloring'], + description="Task status: 'Queueing' (in queue), 'Preparing' (task is preparing), 'Processing' (generating), 'Success' (task completed successfully), or 'Fail' (task failed).", ) - quality: Optional[str] = Field( - None, description='The quality of the edited image', examples=['low'] - ) - size: Optional[str] = Field( - None, description='Size of the output image', examples=['1024x1024'] - ) - user: Optional[str] = Field( + file_id: Optional[str] = Field( None, - description='A unique identifier for end-user monitoring', - examples=['user-1234'], + description='After the task status changes to Success, this field returns the file ID corresponding to the generated video.', + ) + base_resp: MinimaxBaseResponse + + +class BFLFluxProGenerateRequest(BaseModel): + prompt: str = Field(..., description='The text prompt for image generation.') + negative_prompt: Optional[str] = Field( + None, description='The negative prompt for image generation.' + ) + width: int = Field( + ..., description='The width of the image to generate.', ge=64, le=2048 + ) + height: int = Field( + ..., description='The height of the image to generate.', ge=64, le=2048 + ) + num_inference_steps: Optional[int] = Field( + None, description='The number of inference steps.', ge=1, le=100 + ) + guidance_scale: Optional[float] = Field( + None, description='The guidance scale for generation.', ge=1.0, le=20.0 + ) + seed: Optional[int] = Field(None, description='The seed value for reproducibility.') + num_images: Optional[int] = Field( + None, description='The number of images to generate.', ge=1, le=4 ) -class Background(str, Enum): - transparent = 'transparent' - opaque = 'opaque' - - -class Quality(str, Enum): - low = 'low' - medium = 'medium' - high = 'high' - standard = 'standard' - hd = 'hd' - - -class ResponseFormat(str, Enum): - url = 'url' - b64_json = 'b64_json' - - -class Style(str, Enum): - vivid = 'vivid' - natural = 'natural' - - -class OpenAIImageGenerationRequest(BaseModel): - background: Optional[Background] = Field( - None, description='Background transparency', examples=['opaque'] - ) - model: Optional[str] = Field( - None, description='The model to use for image generation', examples=['dall-e-3'] - ) - moderation: Optional[Moderation] = Field( - None, description='Content moderation setting', examples=['auto'] - ) - n: Optional[int] = Field( - None, - description='The number of images to generate (1-10). Only 1 supported for dall-e-3.', - examples=[1], - ) - output_compression: Optional[int] = Field( - None, description='Compression level for JPEG or WebP (0-100)', examples=[100] - ) - output_format: Optional[OutputFormat] = Field( - None, description='Format of the output image', examples=['png'] - ) - prompt: str = Field( - ..., - description='A text description of the desired image', - examples=['Draw a rocket in front of a blackhole in deep space'], - ) - quality: Optional[Quality] = Field( - None, description='The quality of the generated image', examples=['high'] - ) - response_format: Optional[ResponseFormat] = Field( - None, description='Response format of image data', examples=['b64_json'] - ) - size: Optional[str] = Field( - None, - description='Size of the image (e.g., 1024x1024, 1536x1024, auto)', - examples=['1024x1536'], - ) - style: Optional[Style] = Field( - None, description='Style of the image (only for dall-e-3)', examples=['vivid'] - ) - user: Optional[str] = Field( - None, - description='A unique identifier for end-user monitoring', - examples=['user-1234'], - ) - - -class Datum1(BaseModel): - b64_json: Optional[str] = Field(None, description='Base64 encoded image data') - revised_prompt: Optional[str] = Field(None, description='Revised prompt') - url: Optional[str] = Field(None, description='URL of the image') - - -class InputTokensDetails(BaseModel): - image_tokens: Optional[int] = None - text_tokens: Optional[int] = None - - -class Usage(BaseModel): - input_tokens: Optional[int] = None - input_tokens_details: Optional[InputTokensDetails] = None - output_tokens: Optional[int] = None - total_tokens: Optional[int] = None - - -class OpenAIImageGenerationResponse(BaseModel): - data: Optional[List[Datum1]] = None - usage: Optional[Usage] = None - - -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 BFLFluxProGenerateResponse(BaseModel): + id: str = Field(..., description='The unique identifier for the generation task.') + polling_url: str = Field(..., description='URL to poll for the generation result.') class RecraftImageGenerationRequest(BaseModel): - model: str = Field( - ..., description='The model to use for generation (e.g., "recraftv3")' - ) - n: conint(ge=1, le=4) = Field(..., description='The number of images to generate') prompt: str = Field( ..., description='The text prompt describing the image to generate' ) - size: str = Field( - ..., description='The size of the generated image (e.g., "1024x1024")' + model: str = Field( + ..., description='The model to use for generation (e.g., "recraftv3")' ) style: Optional[str] = Field( None, description='The style to apply to the generated image (e.g., "digital_illustration")', ) + size: str = Field( + ..., description='The size of the generated image (e.g., "1024x1024")' + ) + n: int = Field(..., description='The number of images to generate', ge=1, le=4) -class Datum2(BaseModel): +class Datum1(BaseModel): image_id: Optional[str] = Field( None, description='Unique identifier for the generated image' ) @@ -1359,289 +492,37 @@ class RecraftImageGenerationResponse(BaseModel): ..., description='Unix timestamp when the generation was created' ) credits: int = Field(..., description='Number of credits used for the generation') - data: List[Datum2] = Field(..., description='Array of generated image information') + data: List[Datum1] = Field(..., description='Array of generated image information') -class RunwayAspectRatioEnum(str, Enum): - field_1280_720 = '1280:720' - field_720_1280 = '720:1280' - field_1104_832 = '1104:832' - field_832_1104 = '832:1104' - field_960_960 = '960:960' - field_1584_672 = '1584:672' - field_1280_768 = '1280:768' - field_768_1280 = '768:1280' - - -class RunwayDurationEnum(int, Enum): - integer_5 = 5 - integer_10 = 10 - - -class RunwayImageToVideoResponse(BaseModel): - id: Optional[str] = Field(None, description='Task ID') - - -class RunwayModelEnum(str, Enum): - gen4_turbo = 'gen4_turbo' - gen3a_turbo = 'gen3a_turbo' - - -class Position(str, Enum): - first = 'first' - last = 'last' - - -class RunwayPromptImageDetailedObject(BaseModel): - position: Position = Field( +class KlingErrorResponse(BaseModel): + code: int = Field( ..., - description="The position of the image in the output video. 'last' is currently supported for gen3a_turbo only.", + description='- 1000: Authentication failed\n- 1001: Authorization is empty\n- 1002: Authorization is invalid\n- 1003: Authorization is not yet valid\n- 1004: Authorization has expired\n- 1100: Account exception\n- 1101: Account in arrears (postpaid scenario)\n- 1102: Resource pack depleted or expired (prepaid scenario)\n- 1103: Unauthorized access to requested resource\n- 1200: Invalid request parameters\n- 1201: Invalid parameters\n- 1202: Invalid request method\n- 1203: Requested resource does not exist\n- 1300: Trigger platform strategy\n- 1301: Trigger content security policy\n- 1302: API request too frequent\n- 1303: Concurrency/QPS exceeds limit\n- 1304: Trigger IP whitelist policy\n- 5000: Internal server error\n- 5001: Service temporarily unavailable\n- 5002: Server internal timeout\n', ) - uri: AnyUrl = Field( - ..., description='A HTTPS URL or data URI containing an encoded image.' + message: str = Field(..., description='Human-readable error message') + request_id: str = Field( + ..., description='Request ID for tracking and troubleshooting' ) -class RunwayPromptImageObject( - RootModel[Union[AnyUrl, List[RunwayPromptImageDetailedObject]]] -): - root: Union[AnyUrl, List[RunwayPromptImageDetailedObject]] = Field( - ..., - description='Image(s) to use for the video generation. Can be a single URI or an array of image objects with positions.', - ) - - -class RunwayTaskStatusEnum(str, Enum): - SUCCEEDED = 'SUCCEEDED' - RUNNING = 'RUNNING' - FAILED = 'FAILED' - PENDING = 'PENDING' - CANCELLED = 'CANCELLED' - THROTTLED = 'THROTTLED' - - -class RunwayTaskStatusResponse(BaseModel): - createdAt: Optional[datetime] = Field(None, description='Task creation timestamp') - id: Optional[str] = Field(None, description='Task ID') - output: Optional[List[str]] = Field(None, description='Array of output video URLs') - status: Optional[RunwayTaskStatusEnum] = None - - -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 StripeAddress(BaseModel): - city: Optional[str] = None - country: Optional[str] = None - line1: Optional[str] = None - line2: Optional[str] = None - postal_code: Optional[str] = None - state: Optional[str] = None - - -class StripeAmountDetails(BaseModel): - tip: Optional[Dict[str, Any]] = None - - -class StripeBillingDetails(BaseModel): - address: Optional[StripeAddress] = None - email: Optional[str] = None - name: Optional[str] = None - phone: Optional[str] = None - tax_id: Optional[Any] = None - - -class Checks(BaseModel): - address_line1_check: Optional[Any] = None - address_postal_code_check: Optional[Any] = None - cvc_check: Optional[str] = None - - -class ExtendedAuthorization(BaseModel): - status: Optional[str] = None - - -class IncrementalAuthorization(BaseModel): - status: Optional[str] = None - - -class Multicapture(BaseModel): - status: Optional[str] = None - - -class NetworkToken(BaseModel): - used: Optional[bool] = None - - -class Overcapture(BaseModel): - maximum_amount_capturable: Optional[int] = None - status: Optional[str] = None - - -class StripeCardDetails(BaseModel): - amount_authorized: Optional[int] = None - authorization_code: Optional[Any] = None - brand: Optional[str] = None - checks: Optional[Checks] = None - country: Optional[str] = None - exp_month: Optional[int] = None - exp_year: Optional[int] = None - extended_authorization: Optional[ExtendedAuthorization] = None - fingerprint: Optional[str] = None - funding: Optional[str] = None - incremental_authorization: Optional[IncrementalAuthorization] = None - installments: Optional[Any] = None - last4: Optional[str] = None - mandate: Optional[Any] = None - multicapture: Optional[Multicapture] = None - network: Optional[str] = None - network_token: Optional[NetworkToken] = None - network_transaction_id: Optional[str] = None - overcapture: Optional[Overcapture] = None - regulated_status: Optional[str] = None - three_d_secure: Optional[Any] = None - wallet: Optional[Any] = None - - -class Object(str, Enum): - charge = 'charge' - - -class Object1(str, Enum): - event = 'event' - - -class Type4(str, Enum): - payment_intent_succeeded = 'payment_intent.succeeded' - - -class StripeOutcome(BaseModel): - advice_code: Optional[Any] = None - network_advice_code: Optional[Any] = None - network_decline_code: Optional[Any] = None - network_status: Optional[str] = None - reason: Optional[Any] = None - risk_level: Optional[str] = None - risk_score: Optional[int] = None - seller_message: Optional[str] = None - type: Optional[str] = None - - -class Object2(str, Enum): - payment_intent = 'payment_intent' - - -class StripePaymentMethodDetails(BaseModel): - card: Optional[StripeCardDetails] = None - type: Optional[str] = None - - -class Card(BaseModel): - installments: Optional[Any] = None - mandate_options: Optional[Any] = None - network: Optional[Any] = None - request_three_d_secure: Optional[str] = None - - -class StripePaymentMethodOptions(BaseModel): - card: Optional[Card] = None - - -class StripeRefundList(BaseModel): - data: Optional[List[Dict[str, Any]]] = None - has_more: Optional[bool] = None - object: Optional[str] = None - total_count: Optional[int] = None - url: Optional[str] = None - - -class StripeRequestInfo(BaseModel): - id: Optional[str] = None - idempotency_key: Optional[str] = None - - -class StripeShipping(BaseModel): - address: Optional[StripeAddress] = None - carrier: Optional[str] = None - name: Optional[str] = None - phone: Optional[str] = None - tracking_number: Optional[str] = None - - -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 Veo2GenVidPollRequest(BaseModel): - operationName: str = Field( - ..., - description='Full operation name (from predict response)', - examples=[ - 'projects/PROJECT_ID/locations/us-central1/publishers/google/models/MODEL_ID/operations/OPERATION_ID' - ], - ) - - -class Video5(BaseModel): - bytesBase64Encoded: Optional[str] = Field( - None, description='Base64-encoded video content' - ) - gcsUri: Optional[str] = Field(None, description='Cloud Storage URI of the video') - mimeType: Optional[str] = Field(None, description='Video MIME type') - - -class Response(BaseModel): - field_type: Optional[str] = Field( - None, - alias='@type', - examples=[ - 'type.googleapis.com/cloud.ai.large_models.vision.GenerateVideoResponse' - ], - ) - raiMediaFilteredCount: Optional[int] = Field( - None, description='Count of media filtered by responsible AI policies' - ) - videos: Optional[List[Video5]] = None - - -class Veo2GenVidPollResponse(BaseModel): - done: Optional[bool] = None - name: Optional[str] = None - response: Optional[Response] = Field( - None, description='The actual prediction response if done is true' - ) - - -class Image2(BaseModel): +class Image(BaseModel): bytesBase64Encoded: str gcsUri: Optional[str] = None mimeType: Optional[str] = None -class Image3(BaseModel): +class Image1(BaseModel): bytesBase64Encoded: Optional[str] = None gcsUri: str mimeType: Optional[str] = None class Instance(BaseModel): - image: Optional[Union[Image2, Image3]] = Field( + prompt: str = Field(..., description='Text description of the video') + image: Optional[Union[Image, Image1]] = Field( None, description='Optional image to guide video generation' ) - prompt: str = Field(..., description='Text description of the video') class PersonGeneration(str, Enum): @@ -1651,8 +532,6 @@ class PersonGeneration(str, Enum): class Parameters(BaseModel): aspectRatio: Optional[str] = Field(None, examples=['16:9']) - durationSeconds: Optional[int] = None - enhancePrompt: Optional[bool] = None negativePrompt: Optional[str] = None personGeneration: Optional[PersonGeneration] = None sampleCount: Optional[int] = None @@ -1660,6 +539,8 @@ class Parameters(BaseModel): storageUri: Optional[str] = Field( None, description='Optional Cloud Storage URI to upload the video' ) + durationSeconds: Optional[int] = None + enhancePrompt: Optional[bool] = None class Veo2GenVidRequest(BaseModel): @@ -1677,336 +558,347 @@ class Veo2GenVidResponse(BaseModel): ) -class WorkflowRunStatus(str, Enum): - WorkflowRunStatusStarted = 'WorkflowRunStatusStarted' - WorkflowRunStatusFailed = 'WorkflowRunStatusFailed' - WorkflowRunStatusCompleted = 'WorkflowRunStatusCompleted' +class Veo2GenVidPollRequest(BaseModel): + operationName: str = Field( + ..., + description='Full operation name (from predict response)', + examples=[ + 'projects/PROJECT_ID/locations/us-central1/publishers/google/models/MODEL_ID/operations/OPERATION_ID' + ], + ) -class ActionJobResult(BaseModel): - action_job_id: Optional[str] = Field( - None, description='Identifier of the job this result belongs to' +class Video2(BaseModel): + gcsUri: Optional[str] = Field(None, description='Cloud Storage URI of the video') + bytesBase64Encoded: Optional[str] = Field( + None, description='Base64-encoded video content' ) - 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') + mimeType: Optional[str] = Field(None, description='Video MIME type') -class LumaGenerationRequest(BaseModel): - aspect_ratio: LumaAspectRatio - callback_url: Optional[AnyUrl] = Field( +class Response(BaseModel): + field_type: Optional[str] = Field( None, - description='The callback URL of the generation, a POST request with Generation object will be sent to the callback URL when the generation is dreaming, completed, or failed', + alias='@type', + examples=[ + 'type.googleapis.com/cloud.ai.large_models.vision.GenerateVideoResponse' + ], ) - duration: LumaVideoModelOutputDuration - generation_type: Optional[GenerationType1] = 'video' - keyframes: Optional[LumaKeyframes] = None - loop: Optional[bool] = Field(None, description='Whether to loop the video') - model: LumaVideoModel - prompt: str = Field(..., description='The prompt of the generation') - resolution: LumaVideoModelOutputResolution + raiMediaFilteredCount: Optional[int] = Field( + None, description='Count of media filtered by responsible AI policies' + ) + videos: Optional[List[Video2]] = None -class CharacterRef(BaseModel): - identity0: Optional[LumaImageIdentity] = None +class Veo2GenVidPollResponse(BaseModel): + name: Optional[str] = None + done: Optional[bool] = None + response: Optional[Response] = Field( + None, description='The actual prediction response if done is true' + ) -class LumaImageGenerationRequest(BaseModel): - aspect_ratio: Optional[LumaAspectRatio] = '16:9' - callback_url: Optional[AnyUrl] = Field( - None, description='The callback URL for the generation' - ) - character_ref: Optional[CharacterRef] = None - generation_type: Optional[GenerationType2] = 'image' - image_ref: Optional[List[LumaImageRef]] = None - model: Optional[LumaImageModel] = 'photon-1' - modify_image_ref: Optional[LumaModifyImageRef] = None - prompt: Optional[str] = Field(None, description='The prompt of the generation') - style_ref: Optional[List[LumaImageRef]] = None +class RunwayImageToVideoResponse(BaseModel): + id: Optional[str] = Field(None, description='Task ID') -class LumaUpscaleVideoGenerationRequest(BaseModel): - callback_url: Optional[AnyUrl] = Field( - None, description='The callback URL for the upscale' - ) - generation_type: Optional[GenerationType3] = 'upscale_video' - resolution: Optional[LumaVideoModelOutputResolution] = None +class RunwayTaskStatusEnum(str, Enum): + SUCCEEDED = 'SUCCEEDED' + RUNNING = 'RUNNING' + FAILED = 'FAILED' + PENDING = 'PENDING' + CANCELLED = 'CANCELLED' + THROTTLED = 'THROTTLED' -class NodeVersion(BaseModel): - changelog: Optional[str] = Field( - None, description='Summary of changes made in this version' +class RunwayModelEnum(str, Enum): + gen4_turbo = 'gen4_turbo' + gen3a_turbo = 'gen3a_turbo' + + +class Position(str, Enum): + first = 'first' + last = 'last' + + +class RunwayPromptImageDetailedObject(BaseModel): + uri: AnyUrl = Field( + ..., description='A HTTPS URL or data URI containing an encoded image.' ) - comfy_node_extract_status: Optional[str] = Field( - None, description='The status of comfy node extraction process.' + position: Position = Field( + ..., + description="The position of the image in the output video. 'last' is currently supported for gen3a_turbo only.", ) - createdAt: Optional[datetime] = Field( - None, description='The date and time the version was created.' + + +class RunwayDurationEnum(int, Enum): + integer_5 = 5 + integer_10 = 10 + + +class RunwayAspectRatioEnum(str, Enum): + field_1280_720 = '1280:720' + field_720_1280 = '720:1280' + field_1104_832 = '1104:832' + field_832_1104 = '832:1104' + field_960_960 = '960:960' + field_1584_672 = '1584:672' + field_1280_768 = '1280:768' + field_768_1280 = '768:1280' + + +class RunwayPromptImageObject( + RootModel[Union[AnyUrl, List[RunwayPromptImageDetailedObject]]] +): + root: Union[AnyUrl, List[RunwayPromptImageDetailedObject]] = Field( + ..., + description='Image(s) to use for the video generation. Can be a single URI or an array of image objects with positions.', ) - dependencies: Optional[List[str]] = Field( - None, description='A list of pip dependencies required by the node.' + + +class Datum2(BaseModel): + b64_json: Optional[str] = Field(None, description='Base64 encoded image data') + url: Optional[str] = Field(None, description='URL of the image') + revised_prompt: Optional[str] = Field(None, description='Revised prompt') + + +class InputTokensDetails(BaseModel): + text_tokens: Optional[int] = None + image_tokens: Optional[int] = None + + +class Usage(BaseModel): + input_tokens: Optional[int] = None + input_tokens_details: Optional[InputTokensDetails] = None + output_tokens: Optional[int] = None + total_tokens: Optional[int] = None + + +class OpenAIImageGenerationResponse(BaseModel): + data: Optional[List[Datum2]] = None + usage: Optional[Usage] = None + + +class Quality(str, Enum): + low = 'low' + medium = 'medium' + high = 'high' + standard = 'standard' + hd = 'hd' + + +class OutputFormat(str, Enum): + png = 'png' + webp = 'webp' + jpeg = 'jpeg' + + +class Moderation(str, Enum): + low = 'low' + auto = 'auto' + + +class Background(str, Enum): + transparent = 'transparent' + opaque = 'opaque' + + +class ResponseFormat(str, Enum): + url = 'url' + b64_json = 'b64_json' + + +class Style(str, Enum): + vivid = 'vivid' + natural = 'natural' + + +class OpenAIImageGenerationRequest(BaseModel): + model: Optional[str] = Field( + None, description='The model to use for image generation', examples=['dall-e-3'] ) - deprecated: Optional[bool] = Field( - None, description='Indicates if this version is deprecated.' + prompt: str = Field( + ..., + description='A text description of the desired image', + examples=['Draw a rocket in front of a blackhole in deep space'], ) - 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( + n: Optional[int] = Field( None, - description='The version identifier, following semantic versioning. Must be unique for the node.', + description='The number of images to generate (1-10). Only 1 supported for dall-e-3.', + examples=[1], + ) + quality: Optional[Quality] = Field( + None, description='The quality of the generated image', examples=['high'] + ) + size: Optional[str] = Field( + None, + description='Size of the image (e.g., 1024x1024, 1536x1024, auto)', + examples=['1024x1536'], + ) + output_format: Optional[OutputFormat] = Field( + None, description='Format of the output image', examples=['png'] + ) + output_compression: Optional[int] = Field( + None, description='Compression level for JPEG or WebP (0-100)', examples=[100] + ) + moderation: Optional[Moderation] = Field( + None, description='Content moderation setting', examples=['auto'] + ) + background: Optional[Background] = Field( + None, description='Background transparency', examples=['opaque'] + ) + response_format: Optional[ResponseFormat] = Field( + None, description='Response format of image data', examples=['b64_json'] + ) + style: Optional[Style] = Field( + None, description='Style of the image (only for dall-e-3)', examples=['vivid'] + ) + user: Optional[str] = Field( + None, + description='A unique identifier for end-user monitoring', + examples=['user-1234'], ) -class PublisherMember(BaseModel): - id: Optional[str] = Field( - None, description='The unique identifier for the publisher member.' +class OpenAIImageEditRequest(BaseModel): + model: str = Field( + ..., description='The model to use for image editing', examples=['gpt-image-1'] ) - role: Optional[str] = Field( - None, description='The role of the user in the publisher.' + prompt: str = Field( + ..., + description='A text description of the desired edit', + examples=['Give the rocketship rainbow coloring'], ) - user: Optional[PublisherUser] = None + n: Optional[int] = Field( + None, description='The number of images to generate', examples=[1] + ) + quality: Optional[str] = Field( + None, description='The quality of the edited image', examples=['low'] + ) + size: Optional[str] = Field( + None, description='Size of the output image', examples=['1024x1024'] + ) + output_format: Optional[OutputFormat] = Field( + None, description='Format of the output image', examples=['png'] + ) + output_compression: Optional[int] = Field( + None, description='Compression level for JPEG or WebP (0-100)', examples=[100] + ) + moderation: Optional[Moderation] = Field( + None, description='Content moderation setting', examples=['auto'] + ) + background: Optional[str] = Field( + None, description='Background transparency', examples=['opaque'] + ) + user: Optional[str] = Field( + None, + description='A unique identifier for end-user monitoring', + examples=['user-1234'], + ) + + +class AspectRatio2(RootModel[float]): + root: float = Field( + ..., + description='Aspect ratio (width / height)', + ge=0.4, + le=2.5, + title='Aspectratio', + ) + + +class PikaBodyGenerate22T2vGenerate22T2vPost(BaseModel): + promptText: str = Field(..., title='Prompttext') + negativePrompt: Optional[str] = Field(None, title='Negativeprompt') + seed: Optional[int] = Field(None, title='Seed') + resolution: Optional[str] = Field('1080p', title='Resolution') + duration: Optional[int] = Field(5, title='Duration') + aspectRatio: Optional[AspectRatio2] = Field( + None, description='Aspect ratio (width / height)', title='Aspectratio' + ) + + +class PikaGenerateResponse(BaseModel): + video_id: str = Field(..., title='Video Id') + + +class PikaBodyGenerate22I2vGenerate22I2vPost(BaseModel): + image: bytes = Field(..., title='Image') + promptText: Optional[str] = Field(None, title='Prompttext') + negativePrompt: Optional[str] = Field(None, title='Negativeprompt') + seed: Optional[int] = Field(None, title='Seed') + resolution: Optional[str] = Field('1080p', title='Resolution') + duration: Optional[int] = Field(5, title='Duration') + + +class IngredientsMode(str, Enum): + creative = 'creative' + precise = 'precise' + + +class PikaBodyGenerate22C2vGenerate22PikascenesPost(BaseModel): + images: List[bytes] = Field( + ..., description='Array of images to process', title='Images' + ) + ingredientsMode: IngredientsMode = Field(..., title='Ingredientsmode') + promptText: Optional[str] = Field(None, title='Prompttext') + negativePrompt: Optional[str] = Field(None, title='Negativeprompt') + seed: Optional[int] = Field(None, title='Seed') + resolution: Optional[str] = Field('1080p', title='Resolution') + duration: Optional[int] = Field(5, title='Duration') + aspectRatio: Optional[AspectRatio2] = Field( + None, description='Aspect ratio (width / height)', title='Aspectratio' + ) + + +class PikaBodyGenerate22KeyframeGenerate22PikaframesPost(BaseModel): + keyFrames: List[bytes] = Field( + ..., description='Array of keyframe images', title='Keyframes' + ) + promptText: str = Field(..., title='Prompttext') + negativePrompt: Optional[str] = Field(None, title='Negativeprompt') + seed: Optional[int] = Field(None, title='Seed') + resolution: Optional[str] = Field('1080p', title='Resolution') + duration: Optional[int] = Field(5, title='Duration') + + +class PikaVideoResponse(BaseModel): + id: str = Field(..., title='Id') + status: str = Field(..., title='Status') + url: str = Field(..., title='Url') + progress: int = Field(..., title='Progress') + + +class PikaValidationError(BaseModel): + loc: List[Union[str, int]] = Field(..., title='Location') + msg: str = Field(..., title='Message') + type: str = Field(..., title='Error Type') class RunwayImageToVideoRequest(BaseModel): - duration: RunwayDurationEnum - model: RunwayModelEnum promptImage: RunwayPromptImageObject - promptText: Optional[constr(max_length=1000)] = Field( - None, description='Text prompt for the generation' + seed: int = Field( + ..., description='Random seed for generation', ge=0, le=4294967295 ) - ratio: RunwayAspectRatioEnum - seed: conint(ge=0, le=4294967295) = Field( - ..., description='Random seed for generation' + model: RunwayModelEnum = Field(..., description='Model to use for generation') + promptText: Optional[str] = Field( + None, description='Text prompt for the generation', max_length=1000 + ) + duration: RunwayDurationEnum = Field( + ..., description='The number of seconds of duration for the output video.' + ) + ratio: RunwayAspectRatioEnum = Field( + ..., + description='The resolution (aspect ratio) of the output video. Allowable values depend on the selected model. 1280:768 and 768:1280 are only supported for gen3a_turbo.', ) -class StripeCharge(BaseModel): - amount: Optional[int] = None - amount_captured: Optional[int] = None - amount_refunded: Optional[int] = None - application: Optional[str] = None - application_fee: Optional[str] = None - application_fee_amount: Optional[int] = None - balance_transaction: Optional[str] = None - billing_details: Optional[StripeBillingDetails] = None - calculated_statement_descriptor: Optional[str] = None - captured: Optional[bool] = None - created: Optional[int] = None - currency: Optional[str] = None - customer: Optional[str] = None - description: Optional[str] = None - destination: Optional[Any] = None - dispute: Optional[Any] = None - disputed: Optional[bool] = None - failure_balance_transaction: Optional[Any] = None - failure_code: Optional[Any] = None - failure_message: Optional[Any] = None - fraud_details: Optional[Dict[str, Any]] = None - id: Optional[str] = None - invoice: Optional[Any] = None - livemode: Optional[bool] = None - metadata: Optional[Dict[str, Any]] = None - object: Optional[Object] = None - on_behalf_of: Optional[Any] = None - order: Optional[Any] = None - outcome: Optional[StripeOutcome] = None - paid: Optional[bool] = None - payment_intent: Optional[str] = None - payment_method: Optional[str] = None - payment_method_details: Optional[StripePaymentMethodDetails] = None - radar_options: Optional[Dict[str, Any]] = None - receipt_email: Optional[str] = None - receipt_number: Optional[str] = None - receipt_url: Optional[str] = None - refunded: Optional[bool] = None - refunds: Optional[StripeRefundList] = None - review: Optional[Any] = None - shipping: Optional[StripeShipping] = None - source: Optional[Any] = None - source_transfer: Optional[Any] = None - statement_descriptor: Optional[Any] = None - statement_descriptor_suffix: Optional[Any] = None - status: Optional[str] = None - transfer_data: Optional[Any] = None - transfer_group: Optional[Any] = None +class RunwayTaskStatusResponse(BaseModel): + id: Optional[str] = Field(None, description='Task ID') + status: Optional[RunwayTaskStatusEnum] = Field(None, description='Task status') + createdAt: Optional[datetime] = Field(None, description='Task creation timestamp') + output: Optional[List[str]] = Field(None, description='Array of output video URLs') -class StripeChargeList(BaseModel): - data: Optional[List[StripeCharge]] = None - has_more: Optional[bool] = None - object: Optional[str] = None - total_count: Optional[int] = None - url: Optional[str] = None - - -class StripePaymentIntent(BaseModel): - amount: Optional[int] = None - amount_capturable: Optional[int] = None - amount_details: Optional[StripeAmountDetails] = None - amount_received: Optional[int] = None - application: Optional[str] = None - application_fee_amount: Optional[int] = None - automatic_payment_methods: Optional[Any] = None - canceled_at: Optional[int] = None - cancellation_reason: Optional[str] = None - capture_method: Optional[str] = None - charges: Optional[StripeChargeList] = None - client_secret: Optional[str] = None - confirmation_method: Optional[str] = None - created: Optional[int] = None - currency: Optional[str] = None - customer: Optional[str] = None - description: Optional[str] = None - id: Optional[str] = None - invoice: Optional[str] = None - last_payment_error: Optional[Any] = None - latest_charge: Optional[str] = None - livemode: Optional[bool] = None - metadata: Optional[Dict[str, Any]] = None - next_action: Optional[Any] = None - object: Optional[Object2] = None - on_behalf_of: Optional[Any] = None - payment_method: Optional[str] = None - payment_method_configuration_details: Optional[Any] = None - payment_method_options: Optional[StripePaymentMethodOptions] = None - payment_method_types: Optional[List[str]] = None - processing: Optional[Any] = None - receipt_email: Optional[str] = None - review: Optional[Any] = None - setup_future_usage: Optional[Any] = None - shipping: Optional[StripeShipping] = None - source: Optional[Any] = None - statement_descriptor: Optional[Any] = None - statement_descriptor_suffix: Optional[Any] = None - status: Optional[str] = None - transfer_data: Optional[Any] = None - transfer_group: Optional[Any] = None - - -class LumaGeneration(BaseModel): - assets: Optional[LumaAssets] = None - created_at: Optional[datetime] = Field( - None, description='The date and time when the generation was created' - ) - failure_reason: Optional[str] = Field( - None, description='The reason for the state of the generation' - ) - generation_type: Optional[LumaGenerationType] = None - id: Optional[UUID] = Field(None, description='The ID of the generation') - model: Optional[str] = Field(None, description='The model used for the generation') - request: Optional[ - Union[ - LumaGenerationRequest, - LumaImageGenerationRequest, - LumaUpscaleVideoGenerationRequest, - LumaAudioGenerationRequest, - ] - ] = Field(None, description='The request of the generation') - state: Optional[LumaState] = 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 Data8(BaseModel): - object: Optional[StripePaymentIntent] = None - - -class StripeEvent(BaseModel): - api_version: Optional[str] = None - created: Optional[int] = None - data: Data8 - id: str - livemode: Optional[bool] = None - object: Object1 - pending_webhooks: Optional[int] = None - request: Optional[StripeRequestInfo] = None - type: Type4 - - -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 +class PikaHTTPValidationError(BaseModel): + detail: Optional[List[PikaValidationError]] = Field(None, title='Detail') diff --git a/comfy_api_nodes/apis/client.py b/comfy_api_nodes/apis/client.py index 0d822afb5..e9477737c 100644 --- a/comfy_api_nodes/apis/client.py +++ b/comfy_api_nodes/apis/client.py @@ -523,7 +523,6 @@ class PollingOperation(Generic[T, R]): # Parse response response_obj = self.poll_endpoint.response_model.model_validate(resp) - # Check if task is complete status = self._check_task_status(response_obj) logging.debug(f"[DEBUG] Task Status: {status}") diff --git a/comfy_api_nodes/nodes_kling.py b/comfy_api_nodes/nodes_kling.py new file mode 100644 index 000000000..258ae23b0 --- /dev/null +++ b/comfy_api_nodes/nodes_kling.py @@ -0,0 +1,479 @@ +from inspect import cleandoc +from typing import Union, Optional +import math +import logging +import torch + +from comfy_api_nodes.apis import ( + KlingText2VideoRequest, + KlingText2VideoResponse, + TaskStatus, + CameraControl, + Config as CameraConfig, + Type as CameraType, + Duration, + Mode, + AspectRatio, + ModelName, + KlingImage2VideoRequest, + KlingImage2VideoResponse, +) +from comfy_api_nodes.apis.client import ( + ApiEndpoint, + HttpMethod, + SynchronousOperation, + PollingOperation, + EmptyRequest, +) +from comfy_api_nodes.nodes_api import ( + tensor_to_base64_string, + download_url_to_bytesio, +) +from comfy.comfy_types.node_typing import IO, InputTypeOptions, ComfyNodeABC +from comfy_api.input_impl import VideoFromFile +from comfy_api_nodes.mapper_utils import model_field_to_node_input + +KLING_API_VERSION = "v1" +PATH_TEXT_TO_VIDEO = f"/proxy/kling/{KLING_API_VERSION}/videos/text2video" +PATH_IMAGE_TO_VIDEO = f"/proxy/kling/{KLING_API_VERSION}/videos/image2video" +PATH_VIDEO_EXTEND = f"/proxy/kling/{KLING_API_VERSION}/videos/video-extend" +PATH_LIP_SYNC = f"/proxy/kling/{KLING_API_VERSION}/videos/lip-sync" +PATH_VIDEO_EFFECTS = f"/proxy/kling/{KLING_API_VERSION}/videos/effects" +PATH_CHARACTER_IMAGE = f"/proxy/kling/{KLING_API_VERSION}/images/generations" +PATH_VIRTUAL_TRY_ON = f"/proxy/kling/{KLING_API_VERSION}/images/kolors-virtual-try-on" + + +class KlingApiError(Exception): + """Base exception for Kling API errors.""" + + pass + + +def is_valid_camera_control_configs(configs: list[float]) -> bool: + """Verifies that at least one camera control configuration is non-zero.""" + return any(not math.isclose(value, 0.0) for value in configs) + + +def is_valid_prompt(prompt: str) -> bool: + """Verifies that the prompt is not empty.""" + return bool(prompt) + + +def is_valid_initial_response(response: KlingText2VideoResponse) -> bool: + """Verifies that the initial response contains a task ID.""" + return bool(response.data.task_id) + + +def is_valid_video_response(response: KlingText2VideoResponse) -> bool: + """Verifies that the response contains a task result with at least one video.""" + return ( + response.data.task_result + and response.data.task_result.videos + and len(response.data.task_result.videos) > 0 + ) + + +def is_camera_control_supported(model_name: str, duration: str, mode: str) -> bool: + """`camera_control` is only supported in `pro` mode with `5s` duration and `kling-v1-5`""" + return model_name == "kling-v1-5" and duration == "5" and mode == "pro" + + +def get_camera_control_input_config( + tooltip: str, default: float = 0.0 +) -> tuple[IO, InputTypeOptions]: + """Returns common InputTypeOptions for Kling camera control configurations.""" + input_config = { + "default": default, + "min": -10.0, + "max": 10.0, + "step": 0.25, + "display": "slider", + "tooltip": tooltip, + } + return IO.FLOAT, input_config + + +def _get_camera_control_inputs() -> dict[str, tuple[IO, InputTypeOptions]]: + """Returns a dictionary of camera control inputs common to Kling video generation nodes.""" + return { + "camera_control_type": ( + IO.COMBO, + { + "options": [ + camera_control_type.value for camera_control_type in CameraType + ], + "default": "simple", + "tooltip": "Predefined camera movements type. simple: Customizable camera movement. down_back: Camera descends and moves backward. forward_up: Camera moves forward and tilts up. right_turn_forward: Rotate right and move forward. left_turn_forward: Rotate left and move forward.", + }, + ), + "camera_control_horizontal": get_camera_control_input_config( + "Controls camera's movement along horizontal axis (x-axis). Negative indicates left, positive indicates right" + ), + "camera_control_vertical": get_camera_control_input_config( + "Controls camera's movement along vertical axis (y-axis). Negative indicates downward, positive indicates upward." + ), + "camera_control_pan": get_camera_control_input_config( + "Controls camera's rotation in vertical plane (x-axis). Negative indicates downward rotation, positive indicates upward rotation.", + default=0.5, + ), + "camera_control_roll": get_camera_control_input_config( + "Controls camera's rotation in horizontal plane (y-axis). Negative indicates left rotation, positive indicates right rotation.", + ), + "camera_control_tilt": get_camera_control_input_config( + "Controls camera's rolling amount (z-axis). Negative indicates counterclockwise, positive indicates clockwise.", + ), + "camera_control_zoom": get_camera_control_input_config( + "Controls change in camera's focal length. Negative indicates narrower field of view, positive indicates wider field of view.", + ), + } + + +def download_url_to_video_output(video_url: str) -> tuple[VideoFromFile]: + """Downloads a video from a URL and returns a VIDEO output.""" + video_io = download_url_to_bytesio(video_url) + if video_io is None: + error_msg = f"Failed to download video from {video_url}" + logging.error(error_msg) + raise KlingApiError(error_msg) + return (VideoFromFile(video_io),) + + +class KlingNodeABC(ComfyNodeABC): + """Base class for Kling nodes.""" + + @classmethod + def VALIDATE_INPUTS( + cls, + prompt, + negative_prompt, + camera_control_horizontal, + camera_control_vertical, + camera_control_pan, + camera_control_roll, + camera_control_tilt, + camera_control_zoom, + ) -> Union[str, bool]: + if not is_valid_prompt(prompt): + return "Prompt is required" + if len(prompt) >= 2500: + return "Prompt must be less than 2500 characters" + if negative_prompt and len(negative_prompt) >= 2500: + return "Negative prompt must be less than 2500 characters" + if not is_valid_camera_control_configs( + [ + camera_control_horizontal, + camera_control_vertical, + camera_control_pan, + camera_control_roll, + camera_control_tilt, + camera_control_zoom, + ] + ): + return "Invalid camera control configs" + return True + + DESCRIPTION = cleandoc(__doc__ or "") + FUNCTION = "api_call" + CATEGORY = "api node/video/kling" + API_NODE = True + + +class KlingTextToVideoNode(KlingNodeABC): + """ + Kling Text to Video Node. + """ + + @staticmethod + def poll_for_task_status(task_id: str, auth_token: str) -> KlingText2VideoResponse: + """Polls the Kling API endpoint until the task reaches a terminal state.""" + polling_operation = PollingOperation( + poll_endpoint=ApiEndpoint( + path=f"{PATH_TEXT_TO_VIDEO}/{task_id}", + method=HttpMethod.GET, + request_model=EmptyRequest, + response_model=KlingText2VideoResponse, + ), + completed_statuses=[ + TaskStatus.succeed.value, + ], + failed_statuses=[TaskStatus.failed.value], + status_extractor=lambda response: ( + response.data.task_status.value + if response.data and response.data.task_status + else None + ), + auth_token=auth_token, + ) + return polling_operation.execute() + + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "prompt": model_field_to_node_input( + IO.STRING, KlingText2VideoRequest, "prompt", multiline=True + ), + "negative_prompt": model_field_to_node_input( + IO.STRING, KlingText2VideoRequest, "negative_prompt", multiline=True + ), + "cfg_scale": model_field_to_node_input( + IO.FLOAT, KlingText2VideoRequest, "cfg_scale" + ), + "mode": model_field_to_node_input( + IO.COMBO, KlingText2VideoRequest, "mode", enum_type=Mode + ), + "duration": model_field_to_node_input( + IO.COMBO, KlingText2VideoRequest, "duration", enum_type=Duration + ), + "aspect_ratio": model_field_to_node_input( + IO.COMBO, + KlingText2VideoRequest, + "aspect_ratio", + enum_type=AspectRatio, + ), + **_get_camera_control_inputs(), + }, + "hidden": {"auth_token": "AUTH_TOKEN_COMFY_ORG"}, + } + + RETURN_TYPES = ("VIDEO",) + + def api_call( + self, + prompt: str, + negative_prompt: str, + duration: int, + mode: str, + cfg_scale: float, + aspect_ratio: str, + camera_control_type: str, + camera_control_horizontal: float, + camera_control_vertical: float, + camera_control_pan: float, + camera_control_roll: float, + camera_control_tilt: float, + camera_control_zoom: float, + auth_token: Optional[str] = None, + ) -> tuple[VideoFromFile]: + camera_control = None + if is_camera_control_supported("kling-v1-6", duration, mode): + camera_control = CameraControl( + type=CameraType(camera_control_type), + config=CameraConfig( + horizontal=camera_control_horizontal, + vertical=camera_control_vertical, + pan=camera_control_pan, + roll=camera_control_roll, + tilt=camera_control_tilt, + zoom=camera_control_zoom, + ).model_dump(exclude_none=True), + ) + + initial_operation = SynchronousOperation( + endpoint=ApiEndpoint( + path=PATH_TEXT_TO_VIDEO, + method=HttpMethod.POST, + request_model=KlingText2VideoRequest, + response_model=KlingText2VideoResponse, + ), + request=KlingText2VideoRequest( + prompt=prompt if prompt else None, + negative_prompt=negative_prompt if negative_prompt else None, + duration=Duration(duration), + mode=Mode(mode), + cfg_scale=cfg_scale, + aspect_ratio=AspectRatio(aspect_ratio), + camera_control=camera_control, + ), + auth_token=auth_token, + ) + + initial_response = initial_operation.execute() + if not is_valid_initial_response(initial_response): + error_msg = f"Kling initial request failed. Code: {initial_response.code}, Message: {initial_response.message}, Data: {initial_response.data}" + logging.error(error_msg) + raise KlingApiError(error_msg) + + task_id = initial_response.data.task_id + logging.debug("Kling task submitted. Task ID: %s", task_id) + + final_response = self.poll_for_task_status(task_id, auth_token) + if not is_valid_video_response(final_response): + error_msg = ( + f"Kling task {task_id} succeeded but no video data found in response." + ) + logging.error(error_msg) + raise KlingApiError(error_msg) + + video_url = str(final_response.data.task_result.videos[0].url) + logging.debug("Kling task %s succeeded. Video URL: %s", task_id, video_url) + + return download_url_to_video_output(video_url) + + +class KlingImage2VideoNode(KlingNodeABC): + """ + Kling Image to Video Node. + """ + + @staticmethod + def poll_for_task_status(task_id: str, auth_token: str) -> KlingImage2VideoResponse: + """Polls the Kling API endpoint until the task reaches a terminal state.""" + polling_operation = PollingOperation( + poll_endpoint=ApiEndpoint( + path=f"{PATH_IMAGE_TO_VIDEO}/{task_id}", + method=HttpMethod.GET, + request_model=KlingImage2VideoRequest, + response_model=KlingImage2VideoResponse, + ), + completed_statuses=[TaskStatus.succeed.value], + failed_statuses=[TaskStatus.failed.value], + status_extractor=lambda response: ( + response.data.task_status.value + if response.data and response.data.task_status + else None + ), + auth_token=auth_token, + ) + return polling_operation.execute() + + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "model_name": model_field_to_node_input( + IO.COMBO, KlingImage2VideoRequest, "model_name", enum_type=ModelName + ), + "start_frame": model_field_to_node_input( + IO.IMAGE, KlingImage2VideoRequest, "image" + ), + "prompt": model_field_to_node_input( + IO.STRING, KlingImage2VideoRequest, "prompt", multiline=True + ), + "negative_prompt": model_field_to_node_input( + IO.STRING, + KlingImage2VideoRequest, + "negative_prompt", + multiline=True, + ), + "cfg_scale": model_field_to_node_input( + IO.FLOAT, KlingImage2VideoRequest, "cfg_scale" + ), + "mode": model_field_to_node_input( + IO.COMBO, KlingImage2VideoRequest, "mode", enum_type=Mode + ), + "aspect_ratio": model_field_to_node_input( + IO.COMBO, + KlingImage2VideoRequest, + "aspect_ratio", + enum_type=AspectRatio, + ), + "duration": model_field_to_node_input( + IO.COMBO, KlingImage2VideoRequest, "duration", enum_type=Duration + ), + **_get_camera_control_inputs(), + }, + "optional": { + "end_frame": model_field_to_node_input( + IO.IMAGE, KlingImage2VideoRequest, "image_tail" + ), + }, + "hidden": {"auth_token": "AUTH_TOKEN_COMFY_ORG"}, + } + + RETURN_TYPES = ("VIDEO",) + + def api_call( + self, + model_name: str, + start_frame: torch.Tensor, + prompt: str, + negative_prompt: str, + cfg_scale: float, + mode: str, + aspect_ratio: str, + duration: str, + camera_control_type: str, + camera_control_horizontal: float, + camera_control_vertical: float, + camera_control_pan: float, + camera_control_roll: float, + camera_control_tilt: float, + camera_control_zoom: float, + end_frame: Optional[torch.Tensor] = None, + auth_token: Optional[str] = None, + ) -> tuple[VideoFromFile]: + camera_control = None + if is_camera_control_supported(model_name, duration, mode): + config = None + if camera_control_type != "right_turn_forward": + config = CameraConfig( + horizontal=camera_control_horizontal, + vertical=camera_control_vertical, + pan=camera_control_pan, + roll=camera_control_roll, + tilt=camera_control_tilt, + zoom=camera_control_zoom, + ) + camera_control = CameraControl( + type=CameraType(camera_control_type), + config=config if config else None, + ) + + initial_operation = SynchronousOperation( + endpoint=ApiEndpoint( + path=PATH_IMAGE_TO_VIDEO, + method=HttpMethod.POST, + request_model=KlingImage2VideoRequest, + response_model=KlingImage2VideoResponse, + ), + request=KlingImage2VideoRequest( + model_name=ModelName(model_name), + image=tensor_to_base64_string(start_frame), + image_tail=( + tensor_to_base64_string(end_frame) + if end_frame is not None + else None + ), + prompt=prompt, + negative_prompt=negative_prompt if negative_prompt else None, + cfg_scale=cfg_scale, + mode=Mode(mode), + aspect_ratio=AspectRatio(aspect_ratio), + duration=Duration(duration), + camera_control=camera_control, + ), + auth_token=auth_token, + ) + initial_response = initial_operation.execute() + if not is_valid_initial_response(initial_response): + error_msg = f"Kling initial request failed. Code: {initial_response.code}, Message: {initial_response.message}, Data: {initial_response.data}" + logging.error(error_msg) + raise KlingApiError(error_msg) + + task_id = initial_response.data.task_id + logging.debug("Kling task submitted. Task ID: %s", task_id) + + final_response = KlingImage2VideoNode.poll_for_task_status(task_id, auth_token) + if not is_valid_video_response(final_response): + error_msg = ( + f"Kling task {task_id} succeeded but no video data found in response." + ) + logging.error(error_msg) + raise KlingApiError(error_msg) + + video_url = str(final_response.data.task_result.videos[0].url) + logging.info("Attempting to download video from URL: %s", video_url) + + return download_url_to_video_output(video_url) + + +NODE_CLASS_MAPPINGS = { + "KlingTextToVideoNode": KlingTextToVideoNode, + "KlingImage2VideoNode": KlingImage2VideoNode, +} + +NODE_DISPLAY_NAME_MAPPINGS = { + "KlingTextToVideoNode": "Kling Text to Video", + "KlingImage2VideoNode": "Kling Image to Video", +} diff --git a/nodes.py b/nodes.py index db69fc685..07d121550 100644 --- a/nodes.py +++ b/nodes.py @@ -2263,7 +2263,8 @@ def init_builtin_extra_nodes(): api_nodes_dir = os.path.join(os.path.dirname(os.path.realpath(__file__)), "comfy_api_nodes") api_nodes_files = [ "nodes_api.py", - "nodes_veo2.py" + "nodes_veo2.py", + "nodes_kling.py" ] import_failed = []