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:
@@ -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")
|
||||
|
||||
Reference in New Issue
Block a user