- New APIImageGenerate node (external_api/image): text-to-image and image-to-image via Gemini's Imagen (text-only) and Nano Banana / Nano Banana Pro / 2 (text and multi-reference image-to-image) models. - Omni Flash video now accepts a batch of reference images (not just one), sent in order as separate references in the same call. - Fix Veo/Omni resolution and duration handling: correct 720/1080/4K resolution options per model tier, drop the non-existent veo-3.1-fast-generate-001 id, note Google's 8s lock on 1080/4K. - Fix RunwayML model list: gen4_image/gen4_image_turbo are image models, not video, so drop them from the video node's provider list (tracked as follow-up work to add them to the image node). - Update README and tests accordingly. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014Bwg5aRmgChpwrVnpNgoQm
132 lines
3.1 KiB
Python
132 lines
3.1 KiB
Python
"""Stubs the parts of `comfy_api.latest` our nodes touch.
|
|
|
|
`comfy_api` only exists inside a running ComfyUI install, so unit tests fake
|
|
just enough of its surface (io.Schema/Input/Output/NodeOutput, ComfyAPI,
|
|
InputImpl.VideoFromFile) to import and exercise src/nodes.py in isolation.
|
|
"""
|
|
|
|
import sys
|
|
import types
|
|
|
|
|
|
def _install_fake_comfy_api():
|
|
if "comfy_api.latest" in sys.modules:
|
|
return
|
|
|
|
class _InputSpec:
|
|
def __init__(self, name, **kwargs):
|
|
self.name = name
|
|
self.kwargs = kwargs
|
|
|
|
class _InputFactory:
|
|
@staticmethod
|
|
def Input(name, **kwargs):
|
|
return _InputSpec(name, **kwargs)
|
|
|
|
class _OutputSpec:
|
|
def __init__(self, name=None, **kwargs):
|
|
self.name = name
|
|
self.kwargs = kwargs
|
|
|
|
class _OutputFactory:
|
|
@staticmethod
|
|
def Output(name=None, **kwargs):
|
|
return _OutputSpec(name, **kwargs)
|
|
|
|
class String(_InputFactory):
|
|
pass
|
|
|
|
class Float(_InputFactory):
|
|
pass
|
|
|
|
class Int(_InputFactory):
|
|
pass
|
|
|
|
class Combo(_InputFactory):
|
|
pass
|
|
|
|
class Image(_InputFactory, _OutputFactory):
|
|
pass
|
|
|
|
class _DynamicComboOption:
|
|
def __init__(self, key, inputs):
|
|
self.key = key
|
|
self.inputs = inputs
|
|
|
|
class _DynamicComboInputSpec(_InputSpec):
|
|
def __init__(self, name, options=None, **kwargs):
|
|
super().__init__(name, options=options, **kwargs)
|
|
self.options = options or []
|
|
|
|
class DynamicCombo:
|
|
Option = _DynamicComboOption
|
|
|
|
@staticmethod
|
|
def Input(id, options=None, **kwargs):
|
|
return _DynamicComboInputSpec(id, options=options, **kwargs)
|
|
|
|
class Video(_OutputFactory):
|
|
pass
|
|
|
|
class Hidden:
|
|
unique_id = "unique_id"
|
|
|
|
class Schema:
|
|
def __init__(self, **kwargs):
|
|
self.__dict__.update(kwargs)
|
|
|
|
class NodeOutput:
|
|
def __init__(self, *args):
|
|
self.args = args
|
|
|
|
class ComfyNode:
|
|
hidden = None
|
|
|
|
io = types.SimpleNamespace(
|
|
String=String,
|
|
Float=Float,
|
|
Int=Int,
|
|
Combo=Combo,
|
|
Image=Image,
|
|
DynamicCombo=DynamicCombo,
|
|
Video=Video,
|
|
Hidden=Hidden,
|
|
Schema=Schema,
|
|
NodeOutput=NodeOutput,
|
|
ComfyNode=ComfyNode,
|
|
)
|
|
|
|
class ComfyExtension:
|
|
async def get_node_list(self):
|
|
raise NotImplementedError
|
|
|
|
class _Execution:
|
|
async def set_progress(self, value, max_value, node_id=None, **kwargs):
|
|
return None
|
|
|
|
class ComfyAPI:
|
|
def __init__(self):
|
|
self.execution = _Execution()
|
|
|
|
class VideoFromFile:
|
|
def __init__(self, file_obj):
|
|
self.file_obj = file_obj
|
|
|
|
InputImpl = types.SimpleNamespace(VideoFromFile=VideoFromFile)
|
|
|
|
latest = types.SimpleNamespace(
|
|
io=io,
|
|
ComfyExtension=ComfyExtension,
|
|
ComfyAPI=ComfyAPI,
|
|
InputImpl=InputImpl,
|
|
)
|
|
|
|
comfy_api = types.ModuleType("comfy_api")
|
|
comfy_api.latest = latest
|
|
|
|
sys.modules["comfy_api"] = comfy_api
|
|
sys.modules["comfy_api.latest"] = latest
|
|
|
|
|
|
_install_fake_comfy_api()
|