feat: more optional widgets for video node

add more optional widgets for dynamiccombo, durations, aspect ratios, resolutions, audio, and change first or default func to constraint_values
also move some code
This commit is contained in:
draconicdragon
2025-12-31 19:26:21 +01:00
parent 8c5ccaafa1
commit e1570a2dec
+82 -46
View File
@@ -1,7 +1,7 @@
import logging
from typing import Any, Dict, Iterable
from comfy_api.latest import InputImpl, _io, io
from comfy_api.latest import InputImpl, io
from ..nodes.catalog_utils import video_model_specs
from ..nodes.utils import encode_tensor_for_vision, ensure_prompt_length
@@ -16,9 +16,20 @@ LOG = logging.getLogger(__name__)
class GenerateVideoFromText(io.ComfyNode):
_DEFAULT_DURATIONS = ["4s", "5s", "6s", "8s", "10s", "12s", "14s", "15s", "16s", "18s", "20s"]
_DEFAULT_ASPECT_RATIOS = ["16:9", "9:16", "1:1"]
_DEFAULT_RESOLUTIONS = ["1080p", "720p", "480p"]
@staticmethod
def _option_input_id(model_id: str, field: str) -> str:
sanitized = "".join(ch if ch.isalnum() or ch in {"_", "-"} else "_" for ch in model_id)
return f"{sanitized}__{field}"
@staticmethod
def _constraint_values(value: Iterable | None) -> list[str]:
if not value:
return []
if isinstance(value, (str, bytes)):
normalized = str(value).strip()
return [normalized] if normalized else []
return [str(item) for item in value if item]
@classmethod
def _video_specs(cls) -> Dict[str, Dict[str, Any]]:
@@ -29,11 +40,6 @@ class GenerateVideoFromText(io.ComfyNode):
)
return specs
@staticmethod
def _option_input_id(model_id: str, field: str) -> str:
sanitized = "".join(ch if ch.isalnum() or ch in {"_", "-"} else "_" for ch in model_id)
return f"{sanitized}__{field}"
@classmethod
def _get_option_value(cls, model_payload: Dict[str, Any], model_id: str, field: str) -> Any:
candidates = (
@@ -48,20 +54,31 @@ class GenerateVideoFromText(io.ComfyNode):
return None
@classmethod
def _first_or_default(cls, values: Iterable[str] | None, fallback: list[str]) -> list[str]:
items = list(values) if values else []
return items or list(fallback)
@classmethod
def _build_model_options(cls) -> list[_io.DynamicCombo.Option]:
def _build_model_options(cls) -> list[io.DynamicCombo.Option]:
specs = cls._video_specs()
options: list[_io.DynamicCombo.Option] = []
options: list[io.DynamicCombo.Option] = []
for model_id, spec in sorted(specs.items()):
def _sorted_models_by_group(group: str) -> list[tuple[str, Dict[str, Any]]]:
return sorted(
(
(model_id, spec)
for model_id, spec in specs.items()
if spec.get("constraints", {}).get("model_type") == group
),
key=lambda item: item[0],
)
ordered_specs = [
# didnt know about this, is same as [] + []
*_sorted_models_by_group("text-to-video"),
*_sorted_models_by_group("image-to-video"),
]
for model_id, spec in ordered_specs:
constraints = spec.get("constraints") or {}
aspect_ratios = cls._first_or_default(constraints.get("aspect_ratios"), cls._DEFAULT_ASPECT_RATIOS)
resolutions = cls._first_or_default(constraints.get("resolutions"), cls._DEFAULT_RESOLUTIONS)
durations = cls._first_or_default(constraints.get("durations"), cls._DEFAULT_DURATIONS)
aspect_ratios = cls._constraint_values(constraints.get("aspect_ratios"))
resolutions = cls._constraint_values(constraints.get("resolutions"))
durations = cls._constraint_values(constraints.get("durations"))
audio_default = bool(constraints.get("audio")) if constraints.get("audio") is not None else False
option_inputs: list[io.Input] = []
@@ -74,31 +91,36 @@ class GenerateVideoFromText(io.ComfyNode):
)
)
option_inputs.extend(
[
if durations:
option_inputs.append(
io.Combo.Input(
id=cls._option_input_id(model_id, "duration"),
display_name="durationaa",
display_name="duration",
options=durations,
default=durations[0],
tooltip="Duration allowed by the selected model",
),
)
)
if aspect_ratios:
option_inputs.append(
io.Combo.Input(
cls._option_input_id(model_id, "aspect_ratio"),
display_name="aspect_ratio",
options=aspect_ratios,
default=aspect_ratios[0],
tooltip="Aspect ratios allowed by the selected model",
),
)
)
if resolutions:
option_inputs.append(
io.Combo.Input(
cls._option_input_id(model_id, "resolution"),
display_name="resolution",
options=resolutions,
default=resolutions[0],
tooltip="Resolutions allowed by the selected model",
),
]
)
)
)
if constraints.get("audio_configurable"):
option_inputs.append(
@@ -110,7 +132,7 @@ class GenerateVideoFromText(io.ComfyNode):
)
)
options.append(_io.DynamicCombo.Option(model_id, option_inputs))
options.append(io.DynamicCombo.Option(model_id, option_inputs))
return options
@@ -127,7 +149,7 @@ class GenerateVideoFromText(io.ComfyNode):
display_name="Generate Video from Text (Venice)",
category="venice.ai",
inputs=[
_io.DynamicCombo.Input(
io.DynamicCombo.Input(
"model",
options=model_options,
tooltip="Select a Venice video model to auto-populate valid parameters",
@@ -185,20 +207,30 @@ class GenerateVideoFromText(io.ComfyNode):
raise ValueError("Selected model is missing from the Venice catalog; refresh the catalog and try again.")
constraints = spec.get("constraints") or {}
durations = cls._first_or_default(constraints.get("durations"), cls._DEFAULT_DURATIONS)
aspect_ratios = cls._first_or_default(constraints.get("aspect_ratios"), cls._DEFAULT_ASPECT_RATIOS)
resolutions = cls._first_or_default(constraints.get("resolutions"), cls._DEFAULT_RESOLUTIONS)
durations = cls._constraint_values(constraints.get("durations"))
aspect_ratios = cls._constraint_values(constraints.get("aspect_ratios"))
resolutions = cls._constraint_values(constraints.get("resolutions"))
duration = cls._get_option_value(model, model_id, "duration") or durations[0]
aspect_ratio = cls._get_option_value(model, model_id, "aspect_ratio") or aspect_ratios[0]
resolution = cls._get_option_value(model, model_id, "resolution") or resolutions[0]
duration = cls._get_option_value(model, model_id, "duration")
if durations:
if duration is None:
raise ValueError(f"Model {model_id} requires a duration selection")
if duration not in durations:
raise ValueError(f"Duration '{duration}' is not supported by model {model_id}")
if duration not in durations:
raise ValueError(f"Duration '{duration}' is not supported by model {model_id}")
if aspect_ratio not in aspect_ratios:
raise ValueError(f"Aspect ratio '{aspect_ratio}' is not supported by model {model_id}")
if resolution not in resolutions:
raise ValueError(f"Resolution '{resolution}' is not supported by model {model_id}")
aspect_ratio = cls._get_option_value(model, model_id, "aspect_ratio")
if aspect_ratios:
if aspect_ratio is None:
raise ValueError(f"Model {model_id} requires an aspect ratio selection")
if aspect_ratio not in aspect_ratios:
raise ValueError(f"Aspect ratio '{aspect_ratio}' is not supported by model {model_id}")
resolution = cls._get_option_value(model, model_id, "resolution")
if resolutions:
if resolution is None:
raise ValueError(f"Model {model_id} requires a resolution selection")
if resolution not in resolutions:
raise ValueError(f"Resolution '{resolution}' is not supported by model {model_id}")
audio_configurable = bool(constraints.get("audio_configurable"))
audio_default = bool(constraints.get("audio")) if constraints.get("audio") is not None else False
@@ -209,11 +241,15 @@ class GenerateVideoFromText(io.ComfyNode):
"model": model_id,
"prompt": prompt,
"negative_prompt": negative_prompt,
"duration": duration,
"aspect_ratio": aspect_ratio,
"resolution": resolution,
"audio": audio,
}
if durations:
payload["duration"] = duration
if aspect_ratios:
payload["aspect_ratio"] = aspect_ratio
if resolutions:
payload["resolution"] = resolution
if audio is not None:
payload["audio"] = audio
if constraints.get("model_type") == "image-to-video":
image = cls._get_option_value(model, model_id, "image")