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:
Sirshendu Ganguly
2025-09-03 14:20:18 -04:00
parent 655118a9a1
commit 1fbb853e86
21 changed files with 340 additions and 144 deletions
Vendored
BIN
View File
Binary file not shown.
+4 -7
View File
@@ -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
View File
@@ -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
View File
@@ -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 = ()
+87
View File
@@ -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",
}
+20 -6
View File
@@ -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"):
+2 -1
View File
@@ -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!")
+23 -8
View File
@@ -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
View File
@@ -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",
}
BIN
View File
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