From cda83ccd10b3c5c08a02f374c04d6cbe69209584 Mon Sep 17 00:00:00 2001 From: Yanxi Chen <103548668+YC0101@users.noreply.github.com> Date: Mon, 13 Jul 2026 08:03:51 -0400 Subject: [PATCH] sync: add 3 AtlasCloud models (2026-07-13) - ltx-2.3-quality/text-to-video - ltx-2.3-quality/image-to-video - ltx-2.3-quality/extend-video Co-Authored-By: Claude Opus 4.8 (1M context) --- README.md | 3 + .../video/ltx_2_3_quality_extend_video.py | 116 ++++++++++++++++++ .../nodes/video/ltx_2_3_quality_i2v.py | 105 ++++++++++++++++ .../nodes/video/ltx_2_3_quality_t2v.py | 98 +++++++++++++++ src/atlascloud_comfyui/registry.py | 10 ++ tests/test_new_nodes_2026_07_13.py | 75 +++++++++++ 6 files changed, 407 insertions(+) create mode 100644 src/atlascloud_comfyui/nodes/video/ltx_2_3_quality_extend_video.py create mode 100644 src/atlascloud_comfyui/nodes/video/ltx_2_3_quality_i2v.py create mode 100644 src/atlascloud_comfyui/nodes/video/ltx_2_3_quality_t2v.py create mode 100644 tests/test_new_nodes_2026_07_13.py diff --git a/README.md b/README.md index dd81262..ac4190f 100644 --- a/README.md +++ b/README.md @@ -457,6 +457,9 @@ This node pack focuses on **image / video / edit** — see the full **[node cata | AtlasCloud Grok Imagine Edit | xai/grok-imagine-image/edit | | AtlasCloud GPT Image-2 Edit | openai/gpt-image-2/edit | | AtlasCloud GPT Image-2 Developer Edit | openai/gpt-image-2-developer/edit | +| AtlasCloud LTX 2.3 Quality Text-to-Video | ltx-2.3-quality/text-to-video | +| AtlasCloud LTX 2.3 Quality Image-to-Video | ltx-2.3-quality/image-to-video | +| AtlasCloud LTX 2.3 Quality Extend Video | ltx-2.3-quality/extend-video | > Nodes are continuously expanded as new models are added to AtlasCloud. diff --git a/src/atlascloud_comfyui/nodes/video/ltx_2_3_quality_extend_video.py b/src/atlascloud_comfyui/nodes/video/ltx_2_3_quality_extend_video.py new file mode 100644 index 0000000..43b3808 --- /dev/null +++ b/src/atlascloud_comfyui/nodes/video/ltx_2_3_quality_extend_video.py @@ -0,0 +1,116 @@ +from __future__ import annotations + +from typing import Any, Dict, Tuple + +from ..auth.atlas_client_node import AtlasClientHandle + +_RESOLUTIONS = [ + "square_hd", + "square", + "portrait_3_4", + "portrait_9_16", + "landscape_4_3", + "landscape_16_9", +] + +_DEFAULT_PROMPT = "Continue the scene naturally, maintaining the same style, lighting, and motion dynamics" + + +class AtlasLtx23QualityExtendVideo: + """ + ComfyUI Node: AtlasCloud LTX 2.3 Quality Extend Video (remote API) + Outputs: video_url, prediction_id + """ + + CATEGORY = "AtlasCloud/Video" + FUNCTION = "run" + RETURN_TYPES = ("STRING", "STRING") + RETURN_NAMES = ("video_url", "prediction_id") + + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "atlas_client": ("ATLAS_CLIENT",), + "video_url": ("STRING", {"default": "", "tooltip": "Input video URL or data:video/...;base64,..."}), + "prompt": ("STRING", {"multiline": True, "default": _DEFAULT_PROMPT, "tooltip": "Extended scene description (max 2000 chars)"}), + }, + "optional": { + "extend_direction": ( + ["forward", "backward"], + {"default": "forward", "tooltip": "Forward (append after) or Backward (prepend before)"}, + ), + "num_frames": ( + "INT", + {"default": 81, "min": 9, "max": 481, "tooltip": "Extension frames; multiple of 8 plus 1"}, + ), + "resolution": (_RESOLUTIONS, {"default": "landscape_16_9", "tooltip": "Output resolution preset"}), + "match_input_fps": ("BOOLEAN", {"default": True, "tooltip": "Match the input video FPS"}), + "generate_audio": ("BOOLEAN", {"default": False, "tooltip": "Generate accompanying audio"}), + "randomize_seed": ("BOOLEAN", {"default": True, "tooltip": "开启后每次随机;关闭后使用下方固定 seed"}), + "seed": ("INT", {"default": 0, "min": 0, "max": 2**31 - 1, "tooltip": "固定 seed(仅在随机开关关闭时生效)"}), + "poll_interval_sec": ("FLOAT", {"default": 2.0, "min": 0.5, "max": 10.0, "tooltip": "Polling interval (seconds)"}), + "timeout_sec": ("INT", {"default": 900, "min": 30, "max": 7200, "tooltip": "Timeout (seconds)"}), + }, + } + + def run( + self, + atlas_client: AtlasClientHandle, + video_url: str, + prompt: str, + extend_direction: str = "forward", + num_frames: int = 81, + resolution: str = "landscape_16_9", + match_input_fps: bool = True, + generate_audio: bool = False, + randomize_seed: bool = True, + seed: int = 0, + poll_interval_sec: float = 2.0, + timeout_sec: int = 900, + ) -> Tuple[str, str]: + video_url = (video_url or "").strip() + if not video_url: + raise RuntimeError("video_url is required for LTX 2.3 Quality Extend Video") + + p = (prompt or "").strip() + if not p: + raise RuntimeError("prompt is required for LTX 2.3 Quality Extend Video") + + client = atlas_client.client + + payload: Dict[str, Any] = { + "model": "ltx-2.3-quality/extend-video", + "prompt": p, + "video_url": video_url, + "extend_direction": extend_direction, + "num_frames": int(num_frames), + "resolution": resolution, + "match_input_fps": bool(match_input_fps), + "generate_audio": bool(generate_audio), + } + if not randomize_seed: + payload["seed"] = int(seed) + + prediction_id = client.generate_video(payload) + result = client.poll_prediction( + prediction_id, + poll_interval_sec=float(poll_interval_sec), + timeout_sec=float(timeout_sec), + ) + + outputs = (result.get("data") or {}).get("outputs") or [] + if not outputs: + raise RuntimeError(f"No outputs returned for prediction {prediction_id}: {result}") + + first = outputs[0] + if isinstance(first, dict): + url = first.get("url") or first.get("video") or first.get("output") + if isinstance(url, str) and url.strip(): + return (url, prediction_id) + raise RuntimeError(f"Unexpected output object for prediction {prediction_id}: {first}") + + if not isinstance(first, str): + raise RuntimeError(f"Unexpected output type for prediction {prediction_id}: {type(first).__name__} {first!r}") + + return (first, prediction_id) diff --git a/src/atlascloud_comfyui/nodes/video/ltx_2_3_quality_i2v.py b/src/atlascloud_comfyui/nodes/video/ltx_2_3_quality_i2v.py new file mode 100644 index 0000000..c3ce954 --- /dev/null +++ b/src/atlascloud_comfyui/nodes/video/ltx_2_3_quality_i2v.py @@ -0,0 +1,105 @@ +from __future__ import annotations + +from typing import Any, Dict, Tuple + +from ..auth.atlas_client_node import AtlasClientHandle + +_RESOLUTIONS = [ + "square_hd", + "square", + "portrait_3_4", + "portrait_9_16", + "landscape_4_3", + "landscape_16_9", +] + + +class AtlasLtx23QualityImageToVideo: + """ + ComfyUI Node: AtlasCloud LTX 2.3 Quality Image-to-Video (remote API) + Outputs: video_url, prediction_id + """ + + CATEGORY = "AtlasCloud/Video" + FUNCTION = "run" + RETURN_TYPES = ("STRING", "STRING") + RETURN_NAMES = ("video_url", "prediction_id") + + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "atlas_client": ("ATLAS_CLIENT",), + "prompt": ("STRING", {"multiline": True, "tooltip": "Motion/style prompt (max 2000 chars)"}), + "image_url": ("STRING", {"default": "", "tooltip": "Input image URL or data:image/...;base64,..."}), + }, + "optional": { + "num_frames": ( + "INT", + {"default": 121, "min": 25, "max": 257, "tooltip": "Frames; multiple of 8 plus 1"}, + ), + "resolution": (_RESOLUTIONS, {"default": "landscape_16_9", "tooltip": "Output resolution preset"}), + "generate_audio": ("BOOLEAN", {"default": False, "tooltip": "Generate accompanying audio"}), + "randomize_seed": ("BOOLEAN", {"default": True, "tooltip": "开启后每次随机;关闭后使用下方固定 seed"}), + "seed": ("INT", {"default": 0, "min": 0, "max": 2**31 - 1, "tooltip": "固定 seed(仅在随机开关关闭时生效)"}), + "poll_interval_sec": ("FLOAT", {"default": 2.0, "min": 0.5, "max": 10.0, "tooltip": "Polling interval (seconds)"}), + "timeout_sec": ("INT", {"default": 900, "min": 30, "max": 7200, "tooltip": "Timeout (seconds)"}), + }, + } + + def run( + self, + atlas_client: AtlasClientHandle, + prompt: str, + image_url: str, + num_frames: int = 121, + resolution: str = "landscape_16_9", + generate_audio: bool = False, + randomize_seed: bool = True, + seed: int = 0, + poll_interval_sec: float = 2.0, + timeout_sec: int = 900, + ) -> Tuple[str, str]: + image_url = (image_url or "").strip() + if not image_url: + raise RuntimeError("image_url is required for LTX 2.3 Quality Image-to-Video") + + p = (prompt or "").strip() + if not p: + raise RuntimeError("prompt is required for LTX 2.3 Quality Image-to-Video") + + client = atlas_client.client + + payload: Dict[str, Any] = { + "model": "ltx-2.3-quality/image-to-video", + "prompt": p, + "image_url": image_url, + "num_frames": int(num_frames), + "resolution": resolution, + "generate_audio": bool(generate_audio), + } + if not randomize_seed: + payload["seed"] = int(seed) + + prediction_id = client.generate_video(payload) + result = client.poll_prediction( + prediction_id, + poll_interval_sec=float(poll_interval_sec), + timeout_sec=float(timeout_sec), + ) + + outputs = (result.get("data") or {}).get("outputs") or [] + if not outputs: + raise RuntimeError(f"No outputs returned for prediction {prediction_id}: {result}") + + first = outputs[0] + if isinstance(first, dict): + url = first.get("url") or first.get("video") or first.get("output") + if isinstance(url, str) and url.strip(): + return (url, prediction_id) + raise RuntimeError(f"Unexpected output object for prediction {prediction_id}: {first}") + + if not isinstance(first, str): + raise RuntimeError(f"Unexpected output type for prediction {prediction_id}: {type(first).__name__} {first!r}") + + return (first, prediction_id) diff --git a/src/atlascloud_comfyui/nodes/video/ltx_2_3_quality_t2v.py b/src/atlascloud_comfyui/nodes/video/ltx_2_3_quality_t2v.py new file mode 100644 index 0000000..b083aab --- /dev/null +++ b/src/atlascloud_comfyui/nodes/video/ltx_2_3_quality_t2v.py @@ -0,0 +1,98 @@ +from __future__ import annotations + +from typing import Any, Dict, Tuple + +from ..auth.atlas_client_node import AtlasClientHandle + +_RESOLUTIONS = [ + "square_hd", + "square", + "portrait_3_4", + "portrait_9_16", + "landscape_4_3", + "landscape_16_9", +] + + +class AtlasLtx23QualityTextToVideo: + """ + ComfyUI Node: AtlasCloud LTX 2.3 Quality Text-to-Video (remote API) + Outputs: video_url, prediction_id + """ + + CATEGORY = "AtlasCloud/Video" + FUNCTION = "run" + RETURN_TYPES = ("STRING", "STRING") + RETURN_NAMES = ("video_url", "prediction_id") + + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "atlas_client": ("ATLAS_CLIENT",), + "prompt": ("STRING", {"multiline": True, "tooltip": "Text prompt (max 2000 chars)"}), + }, + "optional": { + "num_frames": ( + "INT", + {"default": 121, "min": 25, "max": 257, "tooltip": "Frames; multiple of 8 plus 1"}, + ), + "resolution": (_RESOLUTIONS, {"default": "landscape_16_9", "tooltip": "Output resolution preset"}), + "generate_audio": ("BOOLEAN", {"default": False, "tooltip": "Generate accompanying audio"}), + "randomize_seed": ("BOOLEAN", {"default": True, "tooltip": "开启后每次随机;关闭后使用下方固定 seed"}), + "seed": ("INT", {"default": 0, "min": 0, "max": 2**31 - 1, "tooltip": "固定 seed(仅在随机开关关闭时生效)"}), + "poll_interval_sec": ("FLOAT", {"default": 2.0, "min": 0.5, "max": 10.0, "tooltip": "Polling interval (seconds)"}), + "timeout_sec": ("INT", {"default": 900, "min": 30, "max": 7200, "tooltip": "Timeout (seconds)"}), + }, + } + + def run( + self, + atlas_client: AtlasClientHandle, + prompt: str, + num_frames: int = 121, + resolution: str = "landscape_16_9", + generate_audio: bool = False, + randomize_seed: bool = True, + seed: int = 0, + poll_interval_sec: float = 2.0, + timeout_sec: int = 900, + ) -> Tuple[str, str]: + p = (prompt or "").strip() + if not p: + raise RuntimeError("prompt is required for LTX 2.3 Quality Text-to-Video") + + client = atlas_client.client + + payload: Dict[str, Any] = { + "model": "ltx-2.3-quality/text-to-video", + "prompt": p, + "num_frames": int(num_frames), + "resolution": resolution, + "generate_audio": bool(generate_audio), + } + if not randomize_seed: + payload["seed"] = int(seed) + + prediction_id = client.generate_video(payload) + result = client.poll_prediction( + prediction_id, + poll_interval_sec=float(poll_interval_sec), + timeout_sec=float(timeout_sec), + ) + + outputs = (result.get("data") or {}).get("outputs") or [] + if not outputs: + raise RuntimeError(f"No outputs returned for prediction {prediction_id}: {result}") + + first = outputs[0] + if isinstance(first, dict): + url = first.get("url") or first.get("video") or first.get("output") + if isinstance(url, str) and url.strip(): + return (url, prediction_id) + raise RuntimeError(f"Unexpected output object for prediction {prediction_id}: {first}") + + if not isinstance(first, str): + raise RuntimeError(f"Unexpected output type for prediction {prediction_id}: {type(first).__name__} {first!r}") + + return (first, prediction_id) diff --git a/src/atlascloud_comfyui/registry.py b/src/atlascloud_comfyui/registry.py index c145d31..3d4fda7 100644 --- a/src/atlascloud_comfyui/registry.py +++ b/src/atlascloud_comfyui/registry.py @@ -265,6 +265,10 @@ from atlascloud_comfyui.nodes.image.ideogram_v4_turbo_t2i import AtlasIdeogramV4TurboTextToImage from atlascloud_comfyui.nodes.image.ideogram_v4_quality_t2i import AtlasIdeogramV4QualityTextToImage +from atlascloud_comfyui.nodes.video.ltx_2_3_quality_t2v import AtlasLtx23QualityTextToVideo +from atlascloud_comfyui.nodes.video.ltx_2_3_quality_i2v import AtlasLtx23QualityImageToVideo +from atlascloud_comfyui.nodes.video.ltx_2_3_quality_extend_video import AtlasLtx23QualityExtendVideo + from atlascloud_comfyui.nodes.video.kling_v16_multi_i2v_pro import AtlasKlingV16MultiI2VPro from atlascloud_comfyui.nodes.video.kling_v16_multi_i2v_standard import AtlasKlingV16MultiI2VStandard from atlascloud_comfyui.nodes.video.kling_v16_i2v_standard import AtlasKlingV16I2VStandard @@ -723,6 +727,9 @@ "AtlasCloud Cosmos 3 Super Image-to-Video": AtlasCosmos3SuperImageToVideo, "AtlasCloud Ideogram V4 Turbo Text-to-Image": AtlasIdeogramV4TurboTextToImage, "AtlasCloud Ideogram V4 Quality Text-to-Image": AtlasIdeogramV4QualityTextToImage, + "AtlasCloud LTX 2.3 Quality Text-to-Video": AtlasLtx23QualityTextToVideo, + "AtlasCloud LTX 2.3 Quality Image-to-Video": AtlasLtx23QualityImageToVideo, + "AtlasCloud LTX 2.3 Quality Extend Video": AtlasLtx23QualityExtendVideo, } @@ -1063,6 +1070,9 @@ "AtlasCloud Cosmos 3 Super Image-to-Video": "AtlasCloud Cosmos 3 Super Image-to-Video", "AtlasCloud Ideogram V4 Turbo Text-to-Image": "AtlasCloud Ideogram V4 Turbo Text-to-Image", "AtlasCloud Ideogram V4 Quality Text-to-Image": "AtlasCloud Ideogram V4 Quality Text-to-Image", + "AtlasCloud LTX 2.3 Quality Text-to-Video": "AtlasCloud LTX 2.3 Quality Text-to-Video", + "AtlasCloud LTX 2.3 Quality Image-to-Video": "AtlasCloud LTX 2.3 Quality Image-to-Video", + "AtlasCloud LTX 2.3 Quality Extend Video": "AtlasCloud LTX 2.3 Quality Extend Video", } diff --git a/tests/test_new_nodes_2026_07_13.py b/tests/test_new_nodes_2026_07_13.py new file mode 100644 index 0000000..33e7c76 --- /dev/null +++ b/tests/test_new_nodes_2026_07_13.py @@ -0,0 +1,75 @@ +"""Metadata-only tests for newly added nodes (2026-07-13). + +LTX 2.3 Quality (text-to-video, image-to-video, extend-video). +These tests MUST NOT require ATLASCLOUD_API_KEY. +""" + + +def test_ltx_23_quality_t2v_metadata(): + from src.atlascloud_comfyui.nodes.video.ltx_2_3_quality_t2v import ( + AtlasLtx23QualityTextToVideo, + ) + + required = AtlasLtx23QualityTextToVideo.INPUT_TYPES()["required"] + assert "atlas_client" in required + assert "prompt" in required + assert AtlasLtx23QualityTextToVideo.RETURN_TYPES == ("STRING", "STRING") + assert AtlasLtx23QualityTextToVideo.CATEGORY == "AtlasCloud/Video" + + +def test_ltx_23_quality_i2v_metadata(): + from src.atlascloud_comfyui.nodes.video.ltx_2_3_quality_i2v import ( + AtlasLtx23QualityImageToVideo, + ) + + required = AtlasLtx23QualityImageToVideo.INPUT_TYPES()["required"] + assert "atlas_client" in required + assert "prompt" in required + assert "image_url" in required + assert AtlasLtx23QualityImageToVideo.RETURN_TYPES == ("STRING", "STRING") + assert AtlasLtx23QualityImageToVideo.CATEGORY == "AtlasCloud/Video" + + +def test_ltx_23_quality_extend_video_metadata(): + from src.atlascloud_comfyui.nodes.video.ltx_2_3_quality_extend_video import ( + AtlasLtx23QualityExtendVideo, + ) + + required = AtlasLtx23QualityExtendVideo.INPUT_TYPES()["required"] + assert "atlas_client" in required + assert "prompt" in required + assert "video_url" in required + assert AtlasLtx23QualityExtendVideo.RETURN_TYPES == ("STRING", "STRING") + assert AtlasLtx23QualityExtendVideo.CATEGORY == "AtlasCloud/Video" + + +def test_new_nodes_2026_07_13_registered(): + from src.atlascloud_comfyui.registry import ( + NODE_CLASS_MAPPINGS, + NODE_DISPLAY_NAME_MAPPINGS, + ) + + for key in ( + "AtlasCloud LTX 2.3 Quality Text-to-Video", + "AtlasCloud LTX 2.3 Quality Image-to-Video", + "AtlasCloud LTX 2.3 Quality Extend Video", + ): + assert key in NODE_CLASS_MAPPINGS + assert key in NODE_DISPLAY_NAME_MAPPINGS + + +def test_new_nodes_2026_07_13_model_ids(): + import inspect + + from src.atlascloud_comfyui.nodes.video.ltx_2_3_quality_t2v import AtlasLtx23QualityTextToVideo + from src.atlascloud_comfyui.nodes.video.ltx_2_3_quality_i2v import AtlasLtx23QualityImageToVideo + from src.atlascloud_comfyui.nodes.video.ltx_2_3_quality_extend_video import AtlasLtx23QualityExtendVideo + + expected = { + AtlasLtx23QualityTextToVideo: "ltx-2.3-quality/text-to-video", + AtlasLtx23QualityImageToVideo: "ltx-2.3-quality/image-to-video", + AtlasLtx23QualityExtendVideo: "ltx-2.3-quality/extend-video", + } + for cls, model_id in expected.items(): + src = inspect.getsource(cls.run) + assert f'"model": "{model_id}"' in src