diff --git a/README.md b/README.md index ec4b0d0..3381459 100644 --- a/README.md +++ b/README.md @@ -115,6 +115,10 @@ This node pack focuses on **image / video / edit** — see the full **[node cata | AtlasCloud VEO3.1 Lite Text-to-Video | google/veo3.1-lite/text-to-video | | AtlasCloud VEO3.1 Fast Text-to-Video | google/veo3.1-fast/text-to-video | | AtlasCloud Gemini Omni Flash Text-to-Video Developer | google/gemini-omni-flash/text-to-video-developer | +| AtlasCloud Gemini Omni Flash Text-to-Video | google/gemini-omni-flash/text-to-video | +| AtlasCloud Gemini Omni Flash Image-to-Video | google/gemini-omni-flash/image-to-video | +| AtlasCloud Gemini Omni Flash Reference-to-Video | google/gemini-omni-flash/reference-to-video | +| AtlasCloud Gemini Omni Flash Video Edit | google/gemini-omni-flash/video-edit | | AtlasCloud Grok Imagine Video Text-to-Video | xai/grok-imagine-video/text-to-video | | AtlasCloud VEO2 Text-to-Video | google/veo2 | | AtlasCloud WAN2.6 Text-to-Video | alibaba/wan-2.6/text-to-video | diff --git a/src/atlascloud_comfyui/nodes/video/google_gemini_omni_flash_i2v.py b/src/atlascloud_comfyui/nodes/video/google_gemini_omni_flash_i2v.py new file mode 100644 index 0000000..fddcf13 --- /dev/null +++ b/src/atlascloud_comfyui/nodes/video/google_gemini_omni_flash_i2v.py @@ -0,0 +1,76 @@ +from __future__ import annotations + +from typing import Any, Dict, Tuple + +from ..auth.atlas_client_node import AtlasClientHandle + + +class AtlasGeminiOmniFlashImageToVideo: + 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 20,000 chars)"}), + "image": ("STRING", {"default": "", "tooltip": "Starting frame image URL/base64 (<=20MB)"}), + }, + "optional": { + "duration": ("INT", {"default": 10, "min": 3, "max": 10, "tooltip": "Duration (seconds)"}), + "aspect_ratio": (["16:9", "9:16"], {"default": "16:9", "tooltip": "Aspect ratio"}), + "resolution": (["720p"], {"default": "720p", "tooltip": "Resolution"}), + "thinking_level": (["default", "high", "low"], {"default": "default", "tooltip": "Internal reasoning level"}), + "seed": ("INT", {"default": -1, "min": -1, "max": 2**31 - 1, "tooltip": "Random if -1"}), + "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: str, + duration: int = 10, + aspect_ratio: str = "16:9", + resolution: str = "720p", + thinking_level: str = "default", + seed: int = -1, + poll_interval_sec: float = 2.0, + timeout_sec: int = 900, + ) -> Tuple[str, str]: + prompt = (prompt or "").strip() + if not prompt: + raise RuntimeError("prompt is required") + + image = (image or "").strip() + if not image: + raise RuntimeError("image is required") + + client = atlas_client.client + + payload: Dict[str, Any] = { + "model": "google/gemini-omni-flash/image-to-video", + "prompt": prompt, + "image": image, + "duration": int(duration), + "aspect_ratio": aspect_ratio, + "resolution": resolution, + "thinking_level": thinking_level, + } + + if int(seed) >= 0: + 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}") + + return (outputs[0], prediction_id) diff --git a/src/atlascloud_comfyui/nodes/video/google_gemini_omni_flash_r2v.py b/src/atlascloud_comfyui/nodes/video/google_gemini_omni_flash_r2v.py new file mode 100644 index 0000000..39f8b09 --- /dev/null +++ b/src/atlascloud_comfyui/nodes/video/google_gemini_omni_flash_r2v.py @@ -0,0 +1,78 @@ +from __future__ import annotations + +from typing import Any, Dict, List, Tuple + +from ..auth.atlas_client_node import AtlasClientHandle + + +class AtlasGeminiOmniFlashReferenceToVideo: + 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 20,000 chars)"}), + "images": ("STRING", {"multiline": True, "default": "", "tooltip": "1-5 reference image URLs/base64, one per line"}), + }, + "optional": { + "duration": ("INT", {"default": 10, "min": 3, "max": 10, "tooltip": "Duration (seconds)"}), + "aspect_ratio": (["16:9", "9:16"], {"default": "16:9", "tooltip": "Aspect ratio"}), + "resolution": (["720p"], {"default": "720p", "tooltip": "Resolution"}), + "thinking_level": (["default", "high", "low"], {"default": "default", "tooltip": "Internal reasoning level"}), + "seed": ("INT", {"default": -1, "min": -1, "max": 2**31 - 1, "tooltip": "Random if -1"}), + "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, + images: str, + duration: int = 10, + aspect_ratio: str = "16:9", + resolution: str = "720p", + thinking_level: str = "default", + seed: int = -1, + poll_interval_sec: float = 2.0, + timeout_sec: int = 900, + ) -> Tuple[str, str]: + prompt = (prompt or "").strip() + if not prompt: + raise RuntimeError("prompt is required") + + image_list: List[str] = [v.strip() for v in (images or "").splitlines() if v.strip()] + if not image_list: + raise RuntimeError("images is required (1-5 lines)") + if len(image_list) > 5: + raise RuntimeError("images maxItems is 5") + + client = atlas_client.client + + payload: Dict[str, Any] = { + "model": "google/gemini-omni-flash/reference-to-video", + "prompt": prompt, + "images": image_list, + "duration": int(duration), + "aspect_ratio": aspect_ratio, + "resolution": resolution, + "thinking_level": thinking_level, + } + + if int(seed) >= 0: + 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}") + + return (outputs[0], prediction_id) diff --git a/src/atlascloud_comfyui/nodes/video/google_gemini_omni_flash_t2v.py b/src/atlascloud_comfyui/nodes/video/google_gemini_omni_flash_t2v.py new file mode 100644 index 0000000..c77c068 --- /dev/null +++ b/src/atlascloud_comfyui/nodes/video/google_gemini_omni_flash_t2v.py @@ -0,0 +1,69 @@ +from __future__ import annotations + +from typing import Any, Dict, Tuple + +from ..auth.atlas_client_node import AtlasClientHandle + + +class AtlasGeminiOmniFlashTextToVideo: + 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 20,000 chars)"}), + }, + "optional": { + "duration": ("INT", {"default": 10, "min": 3, "max": 10, "tooltip": "Duration (seconds)"}), + "aspect_ratio": (["16:9", "9:16"], {"default": "16:9", "tooltip": "Aspect ratio"}), + "resolution": (["720p"], {"default": "720p", "tooltip": "Resolution"}), + "thinking_level": (["default", "high", "low"], {"default": "default", "tooltip": "Internal reasoning level"}), + "seed": ("INT", {"default": -1, "min": -1, "max": 2**31 - 1, "tooltip": "Random if -1"}), + "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, + duration: int = 10, + aspect_ratio: str = "16:9", + resolution: str = "720p", + thinking_level: str = "default", + seed: int = -1, + poll_interval_sec: float = 2.0, + timeout_sec: int = 900, + ) -> Tuple[str, str]: + prompt = (prompt or "").strip() + if not prompt: + raise RuntimeError("prompt is required") + + client = atlas_client.client + + payload: Dict[str, Any] = { + "model": "google/gemini-omni-flash/text-to-video", + "prompt": prompt, + "duration": int(duration), + "aspect_ratio": aspect_ratio, + "resolution": resolution, + "thinking_level": thinking_level, + } + + if int(seed) >= 0: + 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}") + + return (outputs[0], prediction_id) diff --git a/src/atlascloud_comfyui/nodes/video/google_gemini_omni_flash_video_edit.py b/src/atlascloud_comfyui/nodes/video/google_gemini_omni_flash_video_edit.py new file mode 100644 index 0000000..70fcd92 --- /dev/null +++ b/src/atlascloud_comfyui/nodes/video/google_gemini_omni_flash_video_edit.py @@ -0,0 +1,78 @@ +from __future__ import annotations + +from typing import Any, Dict, List, Tuple + +from ..auth.atlas_client_node import AtlasClientHandle + + +class AtlasGeminiOmniFlashVideoEdit: + 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": ("STRING", {"default": "", "tooltip": "Source video URL (<=100MB, <=30s)"}), + "prompt": ("STRING", {"multiline": True, "tooltip": "Edit prompt (max 20,000 chars)"}), + }, + "optional": { + "images": ("STRING", {"multiline": True, "default": "", "tooltip": "Optional 1-5 reference image URLs/base64, one per line"}), + "resolution": (["720p"], {"default": "720p", "tooltip": "Resolution"}), + "thinking_level": (["default", "high", "low"], {"default": "default", "tooltip": "Internal reasoning level"}), + "seed": ("INT", {"default": -1, "min": -1, "max": 2**31 - 1, "tooltip": "Random if -1"}), + "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: str, + prompt: str, + images: str = "", + resolution: str = "720p", + thinking_level: str = "default", + seed: int = -1, + poll_interval_sec: float = 2.0, + timeout_sec: int = 900, + ) -> Tuple[str, str]: + prompt = (prompt or "").strip() + if not prompt: + raise RuntimeError("prompt is required") + + video = (video or "").strip() + if not video: + raise RuntimeError("video is required") + + client = atlas_client.client + + payload: Dict[str, Any] = { + "model": "google/gemini-omni-flash/video-edit", + "video": video, + "prompt": prompt, + "resolution": resolution, + "thinking_level": thinking_level, + } + + image_list: List[str] = [v.strip() for v in (images or "").splitlines() if v.strip()] + if image_list: + if len(image_list) > 5: + raise RuntimeError("images maxItems is 5") + payload["images"] = image_list + + if int(seed) >= 0: + 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}") + + return (outputs[0], prediction_id) diff --git a/src/atlascloud_comfyui/registry.py b/src/atlascloud_comfyui/registry.py index e683252..045fc98 100644 --- a/src/atlascloud_comfyui/registry.py +++ b/src/atlascloud_comfyui/registry.py @@ -90,6 +90,10 @@ from atlascloud_comfyui.nodes.video.google_gemini_omni_flash_t2v_dev import AtlasGeminiOmniFlashTextToVideoDev from atlascloud_comfyui.nodes.video.google_gemini_omni_flash_i2v_dev import AtlasGeminiOmniFlashImageToVideoDev from atlascloud_comfyui.nodes.video.google_gemini_omni_flash_r2v_dev import AtlasGeminiOmniFlashReferenceToVideoDev +from atlascloud_comfyui.nodes.video.google_gemini_omni_flash_t2v import AtlasGeminiOmniFlashTextToVideo +from atlascloud_comfyui.nodes.video.google_gemini_omni_flash_i2v import AtlasGeminiOmniFlashImageToVideo +from atlascloud_comfyui.nodes.video.google_gemini_omni_flash_r2v import AtlasGeminiOmniFlashReferenceToVideo +from atlascloud_comfyui.nodes.video.google_gemini_omni_flash_video_edit import AtlasGeminiOmniFlashVideoEdit from atlascloud_comfyui.nodes.video.xai_grok_imagine_video_t2v import AtlasGrokImagineVideoTextToVideo from atlascloud_comfyui.nodes.video.xai_grok_imagine_video_i2v import AtlasGrokImagineVideoImageToVideo from atlascloud_comfyui.nodes.video.xai_grok_imagine_video_v15_i2v import AtlasGrokImagineVideoV15ImageToVideo @@ -565,6 +569,10 @@ "AtlasCloud Gemini Omni Flash Text-to-Video Developer": AtlasGeminiOmniFlashTextToVideoDev, "AtlasCloud Gemini Omni Flash Image-to-Video Developer": AtlasGeminiOmniFlashImageToVideoDev, "AtlasCloud Gemini Omni Flash Reference-to-Video Developer": AtlasGeminiOmniFlashReferenceToVideoDev, + "AtlasCloud Gemini Omni Flash Text-to-Video": AtlasGeminiOmniFlashTextToVideo, + "AtlasCloud Gemini Omni Flash Image-to-Video": AtlasGeminiOmniFlashImageToVideo, + "AtlasCloud Gemini Omni Flash Reference-to-Video": AtlasGeminiOmniFlashReferenceToVideo, + "AtlasCloud Gemini Omni Flash Video Edit": AtlasGeminiOmniFlashVideoEdit, "AtlasCloud VEO3.1 Reference-to-Video": AtlasVeo31ReferenceToVideo, "AtlasCloud VEO3.1 Image-to-Video": AtlasVeo31ImageToVideo, "AtlasCloud VEO3 Image-to-Video": AtlasVeo3ImageToVideo, @@ -893,6 +901,10 @@ "AtlasCloud Gemini Omni Flash Text-to-Video Developer": "AtlasCloud Gemini Omni Flash Text-to-Video Developer", "AtlasCloud Gemini Omni Flash Image-to-Video Developer": "AtlasCloud Gemini Omni Flash Image-to-Video Developer", "AtlasCloud Gemini Omni Flash Reference-to-Video Developer": "AtlasCloud Gemini Omni Flash Reference-to-Video Developer", + "AtlasCloud Gemini Omni Flash Text-to-Video": "AtlasCloud Gemini Omni Flash Text-to-Video", + "AtlasCloud Gemini Omni Flash Image-to-Video": "AtlasCloud Gemini Omni Flash Image-to-Video", + "AtlasCloud Gemini Omni Flash Reference-to-Video": "AtlasCloud Gemini Omni Flash Reference-to-Video", + "AtlasCloud Gemini Omni Flash Video Edit": "AtlasCloud Gemini Omni Flash Video Edit", "AtlasCloud VEO3.1 Reference-to-Video": "AtlasCloud VEO3.1 Reference-to-Video", "AtlasCloud VEO3.1 Image-to-Video": "AtlasCloud VEO3.1 Image-to-Video", "AtlasCloud VEO3 Image-to-Video": "AtlasCloud VEO3 Image-to-Video", diff --git a/tests/test_new_nodes_2026_07_04.py b/tests/test_new_nodes_2026_07_04.py new file mode 100644 index 0000000..0cfd957 --- /dev/null +++ b/tests/test_new_nodes_2026_07_04.py @@ -0,0 +1,92 @@ +"""Metadata-only tests for newly added nodes (2026-07-04). + +Gemini Omni Flash non-developer video variants (text-to-video, image-to-video, +reference-to-video) plus the new video-edit node. +These tests MUST NOT require ATLASCLOUD_API_KEY. +""" + + +def test_gemini_omni_flash_t2v_metadata(): + from src.atlascloud_comfyui.nodes.video.google_gemini_omni_flash_t2v import ( + AtlasGeminiOmniFlashTextToVideo, + ) + + required = AtlasGeminiOmniFlashTextToVideo.INPUT_TYPES()["required"] + assert "atlas_client" in required + assert "prompt" in required + assert AtlasGeminiOmniFlashTextToVideo.RETURN_TYPES == ("STRING", "STRING") + assert AtlasGeminiOmniFlashTextToVideo.CATEGORY == "AtlasCloud/Video" + + +def test_gemini_omni_flash_i2v_metadata(): + from src.atlascloud_comfyui.nodes.video.google_gemini_omni_flash_i2v import ( + AtlasGeminiOmniFlashImageToVideo, + ) + + required = AtlasGeminiOmniFlashImageToVideo.INPUT_TYPES()["required"] + assert "atlas_client" in required + assert "prompt" in required + assert "image" in required + assert AtlasGeminiOmniFlashImageToVideo.RETURN_TYPES == ("STRING", "STRING") + assert AtlasGeminiOmniFlashImageToVideo.CATEGORY == "AtlasCloud/Video" + + +def test_gemini_omni_flash_r2v_metadata(): + from src.atlascloud_comfyui.nodes.video.google_gemini_omni_flash_r2v import ( + AtlasGeminiOmniFlashReferenceToVideo, + ) + + required = AtlasGeminiOmniFlashReferenceToVideo.INPUT_TYPES()["required"] + assert "atlas_client" in required + assert "prompt" in required + assert "images" in required + assert AtlasGeminiOmniFlashReferenceToVideo.RETURN_TYPES == ("STRING", "STRING") + assert AtlasGeminiOmniFlashReferenceToVideo.CATEGORY == "AtlasCloud/Video" + + +def test_gemini_omni_flash_video_edit_metadata(): + from src.atlascloud_comfyui.nodes.video.google_gemini_omni_flash_video_edit import ( + AtlasGeminiOmniFlashVideoEdit, + ) + + required = AtlasGeminiOmniFlashVideoEdit.INPUT_TYPES()["required"] + assert "atlas_client" in required + assert "video" in required + assert "prompt" in required + assert AtlasGeminiOmniFlashVideoEdit.RETURN_TYPES == ("STRING", "STRING") + assert AtlasGeminiOmniFlashVideoEdit.CATEGORY == "AtlasCloud/Video" + + +def test_new_nodes_2026_07_04_registered(): + from src.atlascloud_comfyui.registry import ( + NODE_CLASS_MAPPINGS, + NODE_DISPLAY_NAME_MAPPINGS, + ) + + for key in ( + "AtlasCloud Gemini Omni Flash Text-to-Video", + "AtlasCloud Gemini Omni Flash Image-to-Video", + "AtlasCloud Gemini Omni Flash Reference-to-Video", + "AtlasCloud Gemini Omni Flash Video Edit", + ): + assert key in NODE_CLASS_MAPPINGS + assert key in NODE_DISPLAY_NAME_MAPPINGS + + +def test_new_nodes_2026_07_04_model_ids(): + import inspect + + from src.atlascloud_comfyui.nodes.video.google_gemini_omni_flash_t2v import AtlasGeminiOmniFlashTextToVideo + from src.atlascloud_comfyui.nodes.video.google_gemini_omni_flash_i2v import AtlasGeminiOmniFlashImageToVideo + from src.atlascloud_comfyui.nodes.video.google_gemini_omni_flash_r2v import AtlasGeminiOmniFlashReferenceToVideo + from src.atlascloud_comfyui.nodes.video.google_gemini_omni_flash_video_edit import AtlasGeminiOmniFlashVideoEdit + + expected = { + AtlasGeminiOmniFlashTextToVideo: "google/gemini-omni-flash/text-to-video", + AtlasGeminiOmniFlashImageToVideo: "google/gemini-omni-flash/image-to-video", + AtlasGeminiOmniFlashReferenceToVideo: "google/gemini-omni-flash/reference-to-video", + AtlasGeminiOmniFlashVideoEdit: "google/gemini-omni-flash/video-edit", + } + for cls, model_id in expected.items(): + src = inspect.getsource(cls.run) + assert f'"model": "{model_id}"' in src