Add steps parameter to audio inference, fix empty video/audio object handling, add Creatify models, and improve VideoObject save_to method

This commit is contained in:
Sirshendu Ganguly
2026-01-12 12:48:19 -05:00
parent 745acacd2b
commit b2efe563b3
7 changed files with 136 additions and 26 deletions
+11 -1
View File
@@ -966,6 +966,8 @@ function audioInferenceToggleHandler(audioInferenceNode) {
const sampleRateWidget = audioInferenceNode.widgets.find(w => w.name === "sampleRate");
const useBitrateWidget = audioInferenceNode.widgets.find(w => w.name === "useBitrate");
const bitrateWidget = audioInferenceNode.widgets.find(w => w.name === "bitrate");
const useStepsWidget = audioInferenceNode.widgets.find(w => w.name === "useSteps");
const stepsWidget = audioInferenceNode.widgets.find(w => w.name === "steps");
// Helper function to toggle widget enabled state (exact same pattern)
function toggleWidgetState(useWidget, paramWidget, paramName) {
@@ -1024,6 +1026,10 @@ function audioInferenceToggleHandler(audioInferenceNode) {
if (useBitrateWidget && bitrateWidget) {
toggleWidgetState(useBitrateWidget, bitrateWidget, "bitrate");
}
if (useStepsWidget && stepsWidget) {
toggleWidgetState(useStepsWidget, stepsWidget, "steps");
}
}
function acceleratorOptionsToggleHandler(acceleratorNode) {
@@ -1748,6 +1754,10 @@ function videoModelSearchFilterHandler(videoModelSearchNode) {
"Bria": [
"bria:60@1 (Bria Video Eraser)",
],
"Creatify": [
"creatify:aurora@fast (Creatify Aurora Avatar Model API (720p))",
"creatify:aurora@0 (Creatify Aurora Avatar Model API (720p))",
],
};
const MODEL_DIMENSIONS = {
@@ -1879,7 +1889,7 @@ function videoModelSearchFilterHandler(videoModelSearchNode) {
"sync:react-1@1": "720p",
"bria:60@1": "720p",
"creatify:aurora@fast": "720p",
"creatify:aurora@0": "480p",
"creatify:aurora@0": "720p",
};
const DEFAULT_DIMENSIONS = {"width": 1024, "height": 576};
+115 -23
View File
@@ -5,6 +5,7 @@ import numpy as np
import librosa
import tempfile
import os
import comfy.model_management
from .utils import runwareUtils as rwUtils
@@ -72,7 +73,7 @@ class RunwareAudioInference:
"default": "URL",
"tooltip": "Output type for the generated audio"
}),
"outputFormat": (["MP3"], {
"outputFormat": (["MP3", "MP4"], {
"default": "MP3",
"tooltip": "Format of the output audio"
}),
@@ -82,6 +83,16 @@ class RunwareAudioInference:
"max": 3,
"tooltip": "Number of audio files to generate"
}),
"useSteps": ("BOOLEAN", {
"tooltip": "Enable to include steps parameter in API request. Disable if your model doesn't support steps.",
"default": False,
}),
"steps": ("INT", {
"tooltip": "Number of inference steps for audio generation. More steps generally result in higher quality but longer generation time.",
"default": 20,
"min": 1,
"max": 100,
}),
},
"optional": {
"inputs": ("RUNWAREAUDIOINFERENCEINPUTS", {
@@ -102,30 +113,102 @@ class RunwareAudioInference:
"""Main function to generate audio using Runware API"""
params = self._extractParameters(kwargs)
genConfig = self._buildGenConfig(params)
genResult = rwUtils.inferenecRequest(genConfig)
self._logResponse(genResult)
self._validateResponse(genResult)
try:
# Debug: Print the request being sent
print(f"[DEBUG] Sending Audio Inference Request:")
print(f"[DEBUG] Request Payload: {rwUtils.safe_json_dumps(genConfig, indent=2)}")
genResult = rwUtils.inferenecRequest(genConfig)
# Debug: Print the response received
print(f"[DEBUG] Received Audio Inference Response:")
print(f"[DEBUG] Response: {rwUtils.safe_json_dumps(genResult, indent=2)}")
print(f"[Debugging] Generation config: {rwUtils.safe_json_dumps(genConfig, indent=2)}")
except Exception as e:
# Re-raise the original error without modification
raise e
audioData = genResult["data"][0]
audioUrl = self._extractAudioUrl(genResult)
# Extract task UUID for polling
taskUUID = genConfig[0]["taskUUID"]
hasAudio = audioUrl is not None
hasVideo = bool(audioData.get("videoURL") or audioData.get("videoBase64Data", False))
if hasAudio:
audioObj = self._downloadAndProcessAudio(audioUrl, params["sampleRate"])
print(f"[DEBUG] Audio URL found, returning audio. Audio URL: {audioUrl}")
return (audioObj, None)
if hasVideo:
videos = rwUtils.convertVideoB64List(genResult, width=None, height=None)
print(f"[DEBUG] No audio URL in response, but video is present. Returning video.")
return (None, videos)
raise Exception("No audio or video data received from API")
# Poll for audio completion
while True:
# Check for interrupt before each poll
comfy.model_management.throw_exception_if_processing_interrupted()
# Poll for audio result
pollResult = rwUtils.pollVideoResult(taskUUID)
print(f"[Debugging] Poll result: {rwUtils.safe_json_dumps(pollResult, indent=2) if isinstance(pollResult, (dict, list)) else 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"]
# Handle both string and dict response content
if isinstance(response_content, str):
detailed_message = response_content
elif isinstance(response_content, dict):
detailed_message = response_content.get("message", str(response_content))
else:
detailed_message = str(response_content)
if detailed_message:
error_message = f"{error_message}\nProvider Error: {detailed_message}"
# Include taskUUID for debugging
task_uuid = error_info.get("taskUUID", "unknown")
raise Exception(f"Audio generation failed (Task: {task_uuid}): {error_message}")
if pollResult and "data" in pollResult and len(pollResult["data"]) > 0:
audioData = pollResult["data"][0]
# Check status directly
if "status" in audioData:
status = audioData["status"]
if status == "success":
audioUrl = self._extractAudioUrl(pollResult)
hasAudio = audioUrl is not None
hasVideo = bool(audioData.get("videoURL") or audioData.get("videoBase64Data", False))
if hasAudio:
audioObj = self._downloadAndProcessAudio(audioUrl, params["sampleRate"])
print(f"[DEBUG] Audio URL found, returning audio. Audio URL: {audioUrl}")
# Return empty video object when only audio is present (prevents errors in downstream nodes)
emptyVideoObj = rwUtils.VideoObject("", width=0, height=0)
return (audioObj, emptyVideoObj)
if hasVideo:
videos = rwUtils.convertVideoB64List(pollResult, width=None, height=None)
print(f"[DEBUG] No audio URL in response, but video is present. Returning video.")
# Extract first video object from tuple (ComfyUI expects single VideoObject, not tuple)
if len(videos) > 0:
# Return empty audio object when only video is present (prevents errors in downstream nodes)
emptyAudioObj = {
"waveform": torch.zeros((1, 1, 1)), # [batch, channels, samples]
"sample_rate": params["sampleRate"]
}
return (emptyAudioObj, videos[0])
else:
raise Exception("No video object found in response")
raise Exception("No audio or video data received from API")
# If status is "processing", continue polling
# Check for interrupt before waiting
comfy.model_management.throw_exception_if_processing_interrupted()
# Wait before next poll (split into smaller chunks to allow more frequent interrupt checks)
for _ in range(10): # 10 x 0.1 second = 1 second total
comfy.model_management.throw_exception_if_processing_interrupted()
rwUtils.time.sleep(0.1)
def _extractParameters(self, kwargs):
"""Extract and validate parameters from kwargs"""
@@ -143,6 +226,8 @@ class RunwareAudioInference:
"outputFormat": kwargs.get("outputFormat", "MP3"),
"negativePrompt": kwargs.get("negativePrompt", ""),
"numberResults": kwargs.get("numberResults", 1),
"steps": kwargs.get("steps", 20),
"useSteps": kwargs.get("useSteps", False),
"inputs": kwargs.get("inputs", None),
"providerSettings": kwargs.get("providerSettings", None),
}
@@ -170,7 +255,7 @@ class RunwareAudioInference:
"model": params["model"],
"outputType": params["outputType"],
"outputFormat": params["outputFormat"],
"deliveryMethod": "sync",
"deliveryMethod": "async",
"includeCost": True,
"numberResults": params["numberResults"],
}]
@@ -205,6 +290,13 @@ class RunwareAudioInference:
else:
print(f"[DEBUG] Skipped duration")
# Add steps parameter only if enabled
if params["useSteps"]:
genConfig[0]["steps"] = params["steps"]
print(f"[DEBUG] Added steps: {params['steps']}")
else:
print(f"[DEBUG] Skipped steps")
# Add optional parameters
if params["negativePrompt"]:
genConfig[0]["negativePrompt"] = params["negativePrompt"]
+5
View File
@@ -596,6 +596,11 @@ class VideoObject:
def save_to(self, filename, **kwargs):
"""Save video to file by downloading from URL with retry logic"""
# If video URL is empty, this is a placeholder object (e.g., when only audio is returned)
if not self.video_url or self.video_url.strip() == "":
print(f"[Video Download] Skipping download - empty video URL (placeholder object)")
return False
max_retries = 10
retry_delays = [2, 5, 10, 15, 20] # Longer delays for retries with larger videos
+2 -2
View File
@@ -92,7 +92,7 @@ class videoModelSearch:
],
"Creatify": [
"creatify:aurora@fast (Creatify Aurora Avatar Model API (720p))",
"creatify:aurora@0 (Creatify Aurora Avatar Model API (480p))",
"creatify:aurora@0 (Creatify Aurora Avatar Model API (720p))",
],
}
@@ -281,7 +281,7 @@ class videoModelSearch:
# Creatify Models
"creatify:aurora@fast": "720p",
"creatify:aurora@0": "480p",
"creatify:aurora@0": "720p",
}
MODEL_ARCHITECTURES = [
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