feat: Add video inference features and improvements
- Add Runware Video Inference node with frame images and reference images support - Add conditional parameter support for steps and CFG scale in image inference - Add useFps parameter for video inference with enable/disabled options for Vidu support
This commit is contained in:
@@ -4,7 +4,7 @@ Welcome to **ComfyUI-Runware**! 🌟 This is the official integration for **[Run
|
||||
|
||||
## 🌟 What is ComfyUI-Runware?
|
||||
|
||||
Imagine creating stunning images, refining details, or even removing backgrounds—all without needing a powerful GPU. With **Runware**, all the heavy lifting happens on our servers. No GPU? No problem! 🖥️💨
|
||||
Imagine creating stunning images or videos, refining details, or even removing backgrounds—all without needing a powerful GPU. With **Runware**, all the heavy lifting happens on our servers. No GPU? No problem! 🖥️💨
|
||||
|
||||
This integration adds a suite of custom nodes to ComfyUI, allowing you to:
|
||||
- Perform advanced image inference tasks, including image generation and editing with powerful models.
|
||||
@@ -100,10 +100,10 @@ Inside the `ComfyUI-Runware` custom node folder, you’ll find a `workflows` fol
|
||||
|
||||
## 🧩 Node Features
|
||||
|
||||
Here’s a breakdown of the amazing nodes included in this integration:
|
||||
Here's a breakdown of the amazing nodes included in this integration:
|
||||
|
||||
- **Runware API Manager**: Set or change your API keys, adjust the max connection timeout, adjust the image output quality and format, and enable or disable image caching directly in ComfyUI—no need to edit config files manually! 🔑
|
||||
- **Runware Image Inference**: Perform advanced tasks like inpainting, outpainting, and more. 🎨
|
||||
- **Runware Image Inference**: Perform advanced tasks like inpainting, outpainting, and more 🎨
|
||||
- **Runware PhotoMakerV2**: Create consistent identities with our photomaker pipeline. 🖼️
|
||||
- **Runware Image Upscale**: Upscale your images up to 4x. 🔍
|
||||
- **Runware Background Removal**: Effortlessly remove image backgrounds. 🧹
|
||||
@@ -121,10 +121,7 @@ Here’s a breakdown of the amazing nodes included in this integration:
|
||||
- **Runware Image Caption**: Generate descriptive text from images for further workflow integration. 🖼️
|
||||
- **Runware IPAdapter**: Use reference images to guide the style and content of generated images. 🖌️
|
||||
- **Runware IPAdapters Combine**: Combine multiple IP-Adapter inputs for sophisticated image conditioning. 🔗
|
||||
|
||||
🎬
|
||||
- **Runware Text to Video**: Generate videos from text prompts using advanced AI models. 🎥
|
||||
- **Runware Image to Video**: Create videos from reference images with optional text guidance. 🖼️➡️🎥
|
||||
- **Runware Video Inference**: Generate videos from text prompts using advanced AI models with support for frame images and reference images. 🎬
|
||||
|
||||
---
|
||||
|
||||
|
||||
+3
-1
@@ -27,8 +27,9 @@ from .modules.vaeSearch import vaeSearch
|
||||
from .modules.referenceImages import referenceImages
|
||||
from .modules.videoInference import txt2vid
|
||||
from .modules.videoModelSearch import videoModelSearch
|
||||
from .modules.frameImages import RunwareFrameImages
|
||||
|
||||
RUNWARE_COMFYUI_VERSION = "1.2.0 Beta"
|
||||
RUNWARE_COMFYUI_VERSION = "1.3.0 Beta"
|
||||
|
||||
RESET_COLOR = "\033[0m"
|
||||
BLUE_COLOR = "\033[94m"
|
||||
@@ -68,6 +69,7 @@ NODE_CLASS_MAPPINGS = {
|
||||
"Runware Reference Images": referenceImages,
|
||||
"Runware Video Inference": txt2vid,
|
||||
"Runware Video Model Search": videoModelSearch,
|
||||
"Runware Frame Images": RunwareFrameImages,
|
||||
}
|
||||
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
|
||||
+62
-62
@@ -1,63 +1,63 @@
|
||||
from .utils import runwareUtils as rwUtils
|
||||
|
||||
class apiManager:
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"API Key": (
|
||||
"STRING",
|
||||
{
|
||||
"tooltip": "Define Or Change Your Runware API Key To Start Using Runware Image Inference Services.\n\n(e.g; BcVr1JVQM8FGzrRwb3GsbCq5ww1QwabV)\n\nIf You Don't Have One, You Can Get It From: https://my.runware.ai/keys",
|
||||
},
|
||||
),
|
||||
"Max Timeout": (
|
||||
"INT",
|
||||
{
|
||||
"tooltip": "Change Runware API Request Timeout In Seconds.\n\n(e.g; 90 Seconds).",
|
||||
"min": 5,
|
||||
"max": 99,
|
||||
"default": int(rwUtils.getTimeout()),
|
||||
},
|
||||
),
|
||||
"Image Output Quality": (
|
||||
"INT",
|
||||
{
|
||||
"tooltip": "Sets the compression quality of the output image. Higher values preserve more quality but increase file size, lower values reduce file size but decrease quality.",
|
||||
"min": 20,
|
||||
"max": 99,
|
||||
"default": int(rwUtils.getOutputQuality()),
|
||||
},
|
||||
),
|
||||
"Image Output Format": (
|
||||
["WEBP", "PNG", "JPEG"],
|
||||
{
|
||||
"tooltip": "Change the Default Image Output Format.",
|
||||
"default": rwUtils.getOutputFormat(),
|
||||
},
|
||||
),
|
||||
"Enable Images Caching": (
|
||||
"BOOLEAN",
|
||||
{
|
||||
"label_on": "Enabled",
|
||||
"label_off": "Disabled",
|
||||
"tooltip": "Enable or disable image caching functionality.\n\nWhen enabled, images will be cached to improve performance and reduce redundant processing.",
|
||||
"default": rwUtils.getEnableImagesCaching(),
|
||||
},
|
||||
),
|
||||
"Min Image Cache Size": (
|
||||
"INT",
|
||||
{
|
||||
"tooltip": "Set the minimum size (in KB) for images to be cached.",
|
||||
"min": 30,
|
||||
"max": 4096,
|
||||
"step": 1,
|
||||
"default": int(rwUtils.getMinImageCacheSize()),
|
||||
},
|
||||
),
|
||||
},
|
||||
}
|
||||
|
||||
DESCRIPTION = "API Managers is a Runware Utility That helps you define or change your API Key, Session Timeout, Image Output Format & Quality, and Image Caching settings From The ComfyUI Interface Without having to adjust the env file locally."
|
||||
CATEGORY = "Runware"
|
||||
from .utils import runwareUtils as rwUtils
|
||||
|
||||
class apiManager:
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"API Key": (
|
||||
"STRING",
|
||||
{
|
||||
"tooltip": "Define Or Change Your Runware API Key To Start Using Runware Image Inference Services.\n\n(e.g; BcVr1JVQM8FGzrRwb3GsbCq5ww1QwabV)\n\nIf You Don't Have One, You Can Get It From: https://my.runware.ai/keys",
|
||||
},
|
||||
),
|
||||
"Max Timeout": (
|
||||
"INT",
|
||||
{
|
||||
"tooltip": "Change Runware API Request Timeout In Seconds.\n\n(e.g; 90 Seconds).",
|
||||
"min": 5,
|
||||
"max": 99,
|
||||
"default": int(rwUtils.getTimeout()),
|
||||
},
|
||||
),
|
||||
"Image Output Quality": (
|
||||
"INT",
|
||||
{
|
||||
"tooltip": "Sets the compression quality of the output image. Higher values preserve more quality but increase file size, lower values reduce file size but decrease quality.",
|
||||
"min": 20,
|
||||
"max": 99,
|
||||
"default": int(rwUtils.getOutputQuality()),
|
||||
},
|
||||
),
|
||||
"Image Output Format": (
|
||||
["WEBP", "PNG", "JPEG"],
|
||||
{
|
||||
"tooltip": "Change the Default Image Output Format.",
|
||||
"default": rwUtils.getOutputFormat(),
|
||||
},
|
||||
),
|
||||
"Enable Images Caching": (
|
||||
"BOOLEAN",
|
||||
{
|
||||
"label_on": "Enabled",
|
||||
"label_off": "Disabled",
|
||||
"tooltip": "Enable or disable image caching functionality.\n\nWhen enabled, images will be cached to improve performance and reduce redundant processing.",
|
||||
"default": rwUtils.getEnableImagesCaching(),
|
||||
},
|
||||
),
|
||||
"Min Image Cache Size": (
|
||||
"INT",
|
||||
{
|
||||
"tooltip": "Set the minimum size (in KB) for images to be cached.",
|
||||
"min": 30,
|
||||
"max": 4096,
|
||||
"step": 1,
|
||||
"default": int(rwUtils.getMinImageCacheSize()),
|
||||
},
|
||||
),
|
||||
},
|
||||
}
|
||||
|
||||
DESCRIPTION = "API Managers is a Runware Utility That helps you define or change your API Key, Session Timeout, Image Output Format & Quality, and Image Caching settings From The ComfyUI Interface Without having to adjust the env file locally."
|
||||
CATEGORY = "Runware"
|
||||
RETURN_TYPES = ()
|
||||
@@ -0,0 +1,87 @@
|
||||
from .utils import runwareUtils as rwUtils
|
||||
|
||||
|
||||
class RunwareFrameImages:
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {},
|
||||
"optional": {
|
||||
"image1": ("IMAGE", {
|
||||
"tooltip": "Frame image that will constrain video content at a specific timeline position. Used for keyframe control.",
|
||||
}),
|
||||
"frame1_position": (["auto", "first", "last", "0", "12", "24", "36", "48", "60", "72", "84", "96", "108", "120"], {
|
||||
"default": "auto",
|
||||
"tooltip": "Frame position: 'auto' (automatic distribution), 'first' (beginning), 'last' (end), or specific frame number (0-120).",
|
||||
}),
|
||||
"image2": ("IMAGE", {
|
||||
"tooltip": "Frame image that will constrain video content at a specific timeline position. Used for keyframe control.",
|
||||
}),
|
||||
"frame2_position": (["auto", "first", "last", "0", "12", "24", "36", "48", "60", "72", "84", "96", "108", "120"], {
|
||||
"default": "auto",
|
||||
"tooltip": "Frame position: 'auto' (automatic distribution), 'first' (beginning), 'last' (end), or specific frame number (0-120).",
|
||||
}),
|
||||
"image3": ("IMAGE", {
|
||||
"tooltip": "Frame image that will constrain video content at a specific timeline position. Used for keyframe control.",
|
||||
}),
|
||||
"frame3_position": (["auto", "first", "last", "0", "12", "24", "36", "48", "60", "72", "84", "96", "108", "120"], {
|
||||
"default": "auto",
|
||||
"tooltip": "Frame position: 'auto' (automatic distribution), 'first' (beginning), 'last' (end), or specific frame number (0-120).",
|
||||
}),
|
||||
"image4": ("IMAGE", {
|
||||
"tooltip": "Frame image that will constrain video content at a specific timeline position. Used for keyframe control.",
|
||||
}),
|
||||
"frame4_position": (["auto", "first", "last", "0", "12", "24", "36", "48", "60", "72", "84", "96", "108", "120"], {
|
||||
"default": "auto",
|
||||
"tooltip": "Frame position: 'auto' (automatic distribution), 'first' (beginning), 'last' (end), or specific frame number (0-120).",
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
DESCRIPTION = "Define keyframes that constrain specific frames within the video timeline. Different from reference images - these control WHEN specific visual content appears, not overall style consistency."
|
||||
FUNCTION = "create_frame_images"
|
||||
RETURN_TYPES = ("RUNWAREFRAMEIMAGES",)
|
||||
RETURN_NAMES = ("Frame Images",)
|
||||
CATEGORY = "Runware"
|
||||
|
||||
def create_frame_images(self, **kwargs):
|
||||
frame_images = []
|
||||
|
||||
# Process each image input
|
||||
for i in range(1, 5): # Support up to 4 images
|
||||
image_key = f"image{i}"
|
||||
position_key = f"frame{i}_position"
|
||||
|
||||
image = kwargs.get(image_key)
|
||||
position = kwargs.get(position_key, "auto")
|
||||
|
||||
if image is not None:
|
||||
# Upload image and get UUID
|
||||
image_uuid = rwUtils.convertTensor2IMGForVideo(image)
|
||||
image_url = f"https://im.runware.ai/image/ii/{image_uuid}.webp"
|
||||
|
||||
frame_data = {
|
||||
"inputImage": image_url
|
||||
}
|
||||
|
||||
# Only add frame position if not auto
|
||||
if position != "auto":
|
||||
# Convert string numbers to integers for API
|
||||
if position.isdigit():
|
||||
frame_data["frame"] = int(position)
|
||||
else:
|
||||
frame_data["frame"] = position # "first" or "last"
|
||||
|
||||
frame_images.append(frame_data)
|
||||
|
||||
return (frame_images,)
|
||||
|
||||
|
||||
# Node class mappings for ComfyUI registration
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
"RunwareFrameImages": RunwareFrameImages,
|
||||
}
|
||||
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"RunwareFrameImages": "Runware Frame Images",
|
||||
}
|
||||
@@ -52,21 +52,29 @@ class txt2img:
|
||||
"step": 64,
|
||||
}),
|
||||
"steps": ("INT", {
|
||||
"tooltip": "The number of steps is the number of iterations the model will perform to generate the image. The higher the number of steps, the more detailed the image will be. However, increasing the number of steps will also increase the time it takes to generate the image and may not always result in a better image.",
|
||||
"tooltip": "The number of steps is the number of iterations the model will perform to generate the image. Only used when 'Use Steps' is enabled.",
|
||||
"default": 4,
|
||||
"min": 1,
|
||||
"max": 100,
|
||||
}),
|
||||
"useSteps": (["enable", "disabled"], {
|
||||
"tooltip": "Enable to include steps parameter in API request. Disable if your model doesn't support steps (like nano banana).",
|
||||
"default": "enable",
|
||||
}),
|
||||
"scheduler": (['Default', 'DDIM', 'DDIMScheduler', 'DDPMScheduler', 'DEISMultistepScheduler', 'DPMSolverSinglestepScheduler', 'DPMSolverMultistepScheduler', 'DPMSolverMultistepInverse', 'DPM++', 'DPM++ Karras', 'DPM++ 2M', 'DPM++ 2M Karras', 'DPM++ 2M SDE Karras', 'DPM++ 2M SDE', 'DPM++ 3M', 'DPM++ 3M Karras', 'DPM++ SDE Karras', 'DPM++ SDE', 'EDMEulerScheduler', 'EDMDPMSolverMultistepScheduler', 'Euler', 'EulerDiscreteScheduler', 'Euler Karras', 'Euler a', 'EulerAncestralDiscreteScheduler', 'FlowMatchEulerDiscreteScheduler', 'Heun', 'HeunDiscreteScheduler', 'Heun Karras', 'IPNDMScheduler', 'KDPM2DiscreteScheduler', 'KDPM2AncestralDiscreteScheduler', 'LCM', 'LCMScheduler', 'LMS', 'LMSDiscreteScheduler', 'LMS Karras', 'PNDMScheduler', 'TCDScheduler', 'UniPC', 'UniPCMultistepScheduler', 'UniPC Karras', 'UniPC 2M', 'UniPC 2M Karras', 'UniPC 3M', 'UniPC 3M Karras'], {
|
||||
"tooltip": "An scheduler is a component that manages the inference process. Different schedulers can be used to achieve different results like more detailed images, faster inference, or more accurate results.",
|
||||
"default": "Default",
|
||||
}),
|
||||
"cfgScale": ("FLOAT", {
|
||||
"tooltip": "Guidance scale represents how closely the images will resemble the prompt or how much freedom the AI model has. Higher values are closer to the prompt. Low values may reduce the quality of the results.",
|
||||
"tooltip": "Guidance scale represents how closely the images will resemble the prompt or how much freedom the AI model has. Only used when 'Use CFG Scale' is enabled.",
|
||||
"default": 6.5,
|
||||
"min": 1.0,
|
||||
"max": 30.0,
|
||||
"step": 0.1,
|
||||
"max": 50.0,
|
||||
"step": 0.5,
|
||||
}),
|
||||
"useCFGScale": (["enable", "disabled"], {
|
||||
"tooltip": "Enable to include CFG scale parameter in API request. Disable if your model doesn't support CFG scale (like nano banana).",
|
||||
"default": "enable",
|
||||
}),
|
||||
"seed": ("INT", {
|
||||
"tooltip": "A value used to randomize the image generation. If you want to make images reproducible (generate the same image multiple times), you can use the same seed value.",
|
||||
@@ -179,8 +187,10 @@ class txt2img:
|
||||
height = kwargs.get("height", 512)
|
||||
width = kwargs.get("width", 512)
|
||||
steps = kwargs.get("steps", 4)
|
||||
useSteps = kwargs.get("useSteps", "enable")
|
||||
scheduler = kwargs.get("scheduler", "Default")
|
||||
cfgScale = kwargs.get("cfgScale", 6.5)
|
||||
useCFGScale = kwargs.get("useCFGScale", "enable")
|
||||
seed = kwargs.get("seed")
|
||||
batchSize = kwargs.get("batchSize", 1)
|
||||
|
||||
@@ -195,8 +205,6 @@ class txt2img:
|
||||
"height": height,
|
||||
"width": width,
|
||||
"model": runwareModel,
|
||||
"steps": steps,
|
||||
"CFGScale": cfgScale,
|
||||
"scheduler": scheduler,
|
||||
"clipSkip": clipSkip,
|
||||
"seed": seed,
|
||||
@@ -210,6 +218,12 @@ class txt2img:
|
||||
# For Debugging Purposes Only
|
||||
print(f"[Debugging] Task UUID: {genConfig[0]['taskUUID']}")
|
||||
|
||||
# Add steps and CFGScale only if enabled
|
||||
if useSteps == "enable":
|
||||
genConfig[0]["steps"] = steps
|
||||
if useCFGScale == "enable":
|
||||
genConfig[0]["CFGScale"] = cfgScale
|
||||
|
||||
if (negativePrompt is not None and negativePrompt != ""):
|
||||
genConfig[0]["negativePrompt"] = negativePrompt
|
||||
if (promptWeighting != "Disabled"):
|
||||
|
||||
@@ -46,7 +46,8 @@ class multiInference:
|
||||
def multiInference(self, **kwargs):
|
||||
tasksData = [kwargs.get(f"Task {i}", None) for i in range(1, 5)]
|
||||
validTasks = [task for task in tasksData if task is not None]
|
||||
|
||||
|
||||
|
||||
if not validTasks:
|
||||
raise Exception("Error: No valid tasks provided for Multi Inference!")
|
||||
|
||||
|
||||
@@ -479,16 +479,22 @@ class VideoObject:
|
||||
|
||||
def save_to(self, filename, **kwargs):
|
||||
"""Save video to file by downloading from URL with retry logic"""
|
||||
max_retries = 10
|
||||
retry_delays = [2, 5, 10, 15, 20] # Longer delays for video server issues
|
||||
|
||||
|
||||
while True:
|
||||
for attempt in range(max_retries):
|
||||
try:
|
||||
response = requests.get(self.video_url, stream=True, timeout=30)
|
||||
print(response)
|
||||
print(f"[Video Download] Attempt {attempt + 1}: {response}")
|
||||
|
||||
if response.status_code == 422:
|
||||
|
||||
time.sleep(1)
|
||||
print(f"[Video Download] Server still processing, waiting...")
|
||||
time.sleep(retry_delays[min(attempt, len(retry_delays) - 1)])
|
||||
continue
|
||||
|
||||
if response.status_code == 502:
|
||||
print(f"[Video Download] Server error (502), retrying in {retry_delays[min(attempt, len(retry_delays) - 1)]} seconds...")
|
||||
time.sleep(retry_delays[min(attempt, len(retry_delays) - 1)])
|
||||
continue
|
||||
|
||||
response.raise_for_status()
|
||||
@@ -497,10 +503,19 @@ class VideoObject:
|
||||
for chunk in response.iter_content(chunk_size=8192):
|
||||
f.write(chunk)
|
||||
|
||||
print(f"[Video Download] Successfully downloaded video to {filename}")
|
||||
return True
|
||||
|
||||
except requests.exceptions.RequestException as e:
|
||||
print(f"[Video Download] Attempt {attempt + 1} failed: {e}")
|
||||
if attempt < max_retries - 1:
|
||||
time.sleep(retry_delays[min(attempt, len(retry_delays) - 1)])
|
||||
continue
|
||||
else:
|
||||
print(f"[Video Download] All {max_retries} attempts failed. Video URL: {self.video_url}")
|
||||
return False
|
||||
except Exception as e:
|
||||
|
||||
print(f"[Video Download] Unexpected error: {e}")
|
||||
return False
|
||||
|
||||
return False
|
||||
@@ -561,8 +576,8 @@ def pollVideoResult(taskUUID):
|
||||
return None
|
||||
|
||||
if "errors" in pollResult:
|
||||
print(f"[Debugging] Poll error: {pollResult['errors'][0]['message']}")
|
||||
return None
|
||||
print(f"[Debugging] Poll error: {pollResult}")
|
||||
return pollResult
|
||||
else:
|
||||
return pollResult
|
||||
except Exception as e:
|
||||
|
||||
+128
-48
@@ -2,6 +2,77 @@ from .utils import runwareUtils as rwUtils
|
||||
from .videoModelSearch import videoModelSearch
|
||||
|
||||
|
||||
class RunwareFrameImages:
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {},
|
||||
"optional": {
|
||||
"image1": ("IMAGE", {
|
||||
"tooltip": "First frame image. When only one image is provided, it becomes the first frame automatically.",
|
||||
}),
|
||||
"frame1_position": (["auto", "first", "last"], {
|
||||
"default": "auto",
|
||||
"tooltip": "Position for the first image. 'auto' uses automatic distribution rules.",
|
||||
}),
|
||||
"image2": ("IMAGE", {
|
||||
"tooltip": "Second frame image. With two images, they become first and last frames automatically.",
|
||||
}),
|
||||
"frame2_position": (["auto", "first", "last"], {
|
||||
"default": "auto",
|
||||
"tooltip": "Position for the second image. 'auto' uses automatic distribution rules.",
|
||||
}),
|
||||
"image3": ("IMAGE", {
|
||||
"tooltip": "Third frame image. Will be distributed between first and last frames.",
|
||||
}),
|
||||
"frame3_position": (["auto", "first", "last"], {
|
||||
"default": "auto",
|
||||
"tooltip": "Position for the third image. 'auto' uses automatic distribution rules.",
|
||||
}),
|
||||
"image4": ("IMAGE", {
|
||||
"tooltip": "Fourth frame image. Will be distributed between first and last frames.",
|
||||
}),
|
||||
"frame4_position": (["auto", "first", "last"], {
|
||||
"default": "auto",
|
||||
"tooltip": "Position for the fourth image. 'auto' uses automatic distribution rules.",
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
DESCRIPTION = "Configure frame images for video generation with precise positioning control. Supports automatic distribution or manual positioning."
|
||||
FUNCTION = "create_frame_images"
|
||||
RETURN_TYPES = ("RUNWAREFRAMEIMAGES",)
|
||||
RETURN_NAMES = ("Frame Images",)
|
||||
CATEGORY = "Runware"
|
||||
|
||||
def create_frame_images(self, **kwargs):
|
||||
frame_images = []
|
||||
|
||||
# Process each image input
|
||||
for i in range(1, 5): # Support up to 4 images
|
||||
image_key = f"image{i}"
|
||||
position_key = f"frame{i}_position"
|
||||
|
||||
image = kwargs.get(image_key)
|
||||
position = kwargs.get(position_key, "auto")
|
||||
|
||||
if image is not None:
|
||||
# Upload image and get UUID
|
||||
image_uuid = rwUtils.convertTensor2IMGForVideo(image)
|
||||
image_url = f"https://im.runware.ai/image/ii/{image_uuid}.webp"
|
||||
|
||||
frame_data = {
|
||||
"inputImage": image_url
|
||||
}
|
||||
|
||||
# Only add frame position if not auto
|
||||
if position != "auto":
|
||||
frame_data["frame"] = position
|
||||
|
||||
frame_images.append(frame_data)
|
||||
|
||||
return (frame_images,)
|
||||
|
||||
|
||||
class txt2vid:
|
||||
@classmethod
|
||||
@@ -58,40 +129,38 @@ class txt2vid:
|
||||
"max": 30,
|
||||
}),
|
||||
"fps": ("INT", {
|
||||
"tooltip": "Frames per second for the generated video.",
|
||||
"tooltip": "Frames per second for the generated video. Only used when 'Use FPS' is enabled.",
|
||||
"default": 24,
|
||||
"min": 8,
|
||||
"max": 60,
|
||||
}),
|
||||
"useFps": (["enable", "disabled"], {
|
||||
"tooltip": "Enable to include fps parameter in API request. Disable if your model doesn't support fps.",
|
||||
"default": "enable",
|
||||
}),
|
||||
"outputFormat": (["mp4", "webm", "mov"], {
|
||||
"default": "mp4",
|
||||
"tooltip": "Choose the output video format.",
|
||||
}),
|
||||
"batchSize": ("INT", {
|
||||
"tooltip": "The number of videos to generate in a single request.",
|
||||
"default": 1,
|
||||
"min": 1,
|
||||
"max": 4,
|
||||
}),
|
||||
"includeCost": ("BOOLEAN", {
|
||||
"tooltip": "Include cost information in the response.",
|
||||
"default": True,
|
||||
"label_on": "Enabled",
|
||||
"label_off": "Disabled",
|
||||
}),
|
||||
"seed": ("INT", {
|
||||
"tooltip": "A value used to randomize the video generation. If you want to make videos reproducible (generate the same video multiple times), you can use the same seed value. Note: Only supported by Wan models.",
|
||||
"default": rwUtils.genRandSeed(),
|
||||
"min": 1,
|
||||
"max": 9223372036854776000,
|
||||
}),
|
||||
"batchSize": ("INT", {
|
||||
"tooltip": "The number of videos to generate in a single request.",
|
||||
"default": 1,
|
||||
"min": 1,
|
||||
"max": 4,
|
||||
}),
|
||||
},
|
||||
"optional": {
|
||||
"firstImage": ("IMAGE", {
|
||||
"tooltip": "Image to use as the first frame of the video.",
|
||||
"frameImages": ("RUNWAREFRAMEIMAGES", {
|
||||
"tooltip": "Frame images configuration from Runware Frame Images node. Allows precise control over frame positioning.",
|
||||
}),
|
||||
"lastImage": ("IMAGE", {
|
||||
"tooltip": "Image to use as the last frame of the video.",
|
||||
"referenceImages": ("RUNWAREREFERENCEIMAGES", {
|
||||
"tooltip": "Connect a Runware Reference Images node to provide reference images for the subject. These reference images help the AI maintain identity consistency during the video generation process.",
|
||||
}),
|
||||
"providerSettings": ("DICT", {
|
||||
"tooltip": "Provider-specific settings for video generation.",
|
||||
@@ -122,16 +191,16 @@ class txt2vid:
|
||||
multiInferenceMode = kwargs.get("Multi Inference Mode", False)
|
||||
promptWeighting = kwargs.get("Prompt Weighting", "Disabled")
|
||||
providerSettings = kwargs.get("providerSettings", None)
|
||||
firstImage = kwargs.get("firstImage", None)
|
||||
lastImage = kwargs.get("lastImage", None)
|
||||
frameImages = kwargs.get("frameImages", None)
|
||||
referenceImages = kwargs.get("referenceImages", None)
|
||||
useCustomDimensions = kwargs.get("useCustomDimensions", False)
|
||||
customWidth = kwargs.get("width", 864)
|
||||
customHeight = kwargs.get("height", 480)
|
||||
duration = kwargs.get("duration", 5)
|
||||
fps = kwargs.get("fps", 24)
|
||||
useFps = kwargs.get("useFps", "enable")
|
||||
outputFormat = kwargs.get("outputFormat", "mp4")
|
||||
batchSize = kwargs.get("batchSize", 1)
|
||||
includeCost = kwargs.get("includeCost", True)
|
||||
seed = kwargs.get("seed", 1)
|
||||
|
||||
# Handle model input - could be dict or string
|
||||
@@ -159,13 +228,16 @@ class txt2vid:
|
||||
"height": height,
|
||||
"width": width,
|
||||
"model": model,
|
||||
"fps": fps,
|
||||
"outputFormat": outputFormat,
|
||||
"numberResults": batchSize,
|
||||
"includeCost": includeCost,
|
||||
"includeCost": True,
|
||||
}
|
||||
]
|
||||
|
||||
# Add fps parameter only if enabled
|
||||
if useFps == "enable":
|
||||
genConfig[0]["fps"] = fps
|
||||
|
||||
# Add duration parameter - unified for all models
|
||||
genConfig[0]["duration"] = duration
|
||||
|
||||
@@ -181,31 +253,14 @@ class txt2vid:
|
||||
genConfig[0]["promptWeighting"] = "sdEmbeds"
|
||||
else:
|
||||
genConfig[0]["promptWeighting"] = "compel"
|
||||
# Build frameImages array from firstImage and lastImage parameters
|
||||
frameImages = []
|
||||
if (firstImage is not None):
|
||||
# Force upload to get UUID for video frame images
|
||||
firstImageUUID = rwUtils.convertTensor2IMGForVideo(firstImage)
|
||||
# Construct full URL with PNG extension
|
||||
firstImageURL = f"https://im.runware.ai/image/ii/{firstImageUUID}.webp"
|
||||
print(f"[Debugging] First image URL: {firstImageURL}")
|
||||
frameImages.append({
|
||||
"inputImage": firstImageURL
|
||||
})
|
||||
|
||||
if (lastImage is not None):
|
||||
# Force upload to get UUID for video frame images
|
||||
lastImageUUID = rwUtils.convertTensor2IMGForVideo(lastImage)
|
||||
# Construct full URL with PNG extension
|
||||
lastImageURL = f"https://im.runware.ai/image/ii/{lastImageUUID}.webp"
|
||||
print(f"[Debugging] Last image URL: {lastImageURL}")
|
||||
frameImages.append({
|
||||
"inputImage": lastImageURL
|
||||
})
|
||||
|
||||
if frameImages:
|
||||
# Add frameImages if provided from Runware Frame Images node
|
||||
if frameImages is not None and len(frameImages) > 0:
|
||||
genConfig[0]["frameImages"] = frameImages
|
||||
print(f"[Debugging] Frame images array: {frameImages}")
|
||||
# Add referenceImages if provided from Runware Reference Images node
|
||||
if referenceImages is not None and len(referenceImages) > 0:
|
||||
genConfig[0]["referenceImages"] = referenceImages
|
||||
print(f"[Debugging] Reference images array: {referenceImages}")
|
||||
if (providerSettings is not None):
|
||||
genConfig[0]["providerSettings"] = providerSettings
|
||||
|
||||
@@ -243,6 +298,20 @@ class txt2vid:
|
||||
pollResult = rwUtils.pollVideoResult(taskUUID)
|
||||
print(f"[Debugging] Poll result: {pollResult}")
|
||||
|
||||
# Check for errors first
|
||||
if pollResult and "errors" in pollResult and len(pollResult["errors"]) > 0:
|
||||
error_info = pollResult["errors"][0]
|
||||
error_message = error_info.get("message", "Unknown error")
|
||||
|
||||
# Extract more detailed error info if available
|
||||
if "responseContent" in error_info:
|
||||
response_content = error_info["responseContent"]
|
||||
detailed_message = response_content.get("message", "")
|
||||
if detailed_message:
|
||||
error_message = f"{error_message}\nProvider Error: {detailed_message}"
|
||||
|
||||
raise Exception(f"Video generation failed: {error_message}")
|
||||
|
||||
if pollResult and "data" in pollResult and len(pollResult["data"]) > 0:
|
||||
video_data = pollResult["data"][0]
|
||||
|
||||
@@ -254,9 +323,20 @@ class txt2vid:
|
||||
if "videoURL" in video_data or "videoBase64Data" in video_data:
|
||||
videos = rwUtils.convertVideoB64List(pollResult, width, height)
|
||||
return videos
|
||||
elif status != "processing":
|
||||
raise Exception(f"Video generation failed: {video_data.get('error', 'Unknown error')}")
|
||||
|
||||
# If status is "processing", continue polling
|
||||
|
||||
# Wait before next poll
|
||||
rwUtils.time.sleep(1)
|
||||
rwUtils.time.sleep(1)
|
||||
|
||||
|
||||
# Node class mappings for ComfyUI registration
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
"RunwareFrameImages": RunwareFrameImages,
|
||||
"txt2vid": txt2vid,
|
||||
}
|
||||
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"RunwareFrameImages": "Runware Frame Images",
|
||||
"txt2vid": "Runware Video Inference",
|
||||
}
|
||||
Vendored
BIN
Binary file not shown.
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user