mirror of
https://github.com/comfyanonymous/ComfyUI.git
synced 2026-04-18 14:32:49 +08:00
Merge upstream/master, keep local README.md
This commit is contained in:
commit
d83a4dbfcf
@ -92,14 +92,23 @@ def seed_from_paths_batch(
|
|||||||
session.execute(ins_asset, chunk)
|
session.execute(ins_asset, chunk)
|
||||||
|
|
||||||
# try to claim AssetCacheState (file_path)
|
# try to claim AssetCacheState (file_path)
|
||||||
winners_by_path: set[str] = set()
|
# Insert with ON CONFLICT DO NOTHING, then query to find which paths were actually inserted
|
||||||
ins_state = (
|
ins_state = (
|
||||||
sqlite.insert(AssetCacheState)
|
sqlite.insert(AssetCacheState)
|
||||||
.on_conflict_do_nothing(index_elements=[AssetCacheState.file_path])
|
.on_conflict_do_nothing(index_elements=[AssetCacheState.file_path])
|
||||||
.returning(AssetCacheState.file_path)
|
|
||||||
)
|
)
|
||||||
for chunk in _iter_chunks(state_rows, _rows_per_stmt(3)):
|
for chunk in _iter_chunks(state_rows, _rows_per_stmt(3)):
|
||||||
winners_by_path.update((session.execute(ins_state, chunk)).scalars().all())
|
session.execute(ins_state, chunk)
|
||||||
|
|
||||||
|
# Query to find which of our paths won (were actually inserted)
|
||||||
|
winners_by_path: set[str] = set()
|
||||||
|
for chunk in _iter_chunks(path_list, MAX_BIND_PARAMS):
|
||||||
|
result = session.execute(
|
||||||
|
sqlalchemy.select(AssetCacheState.file_path)
|
||||||
|
.where(AssetCacheState.file_path.in_(chunk))
|
||||||
|
.where(AssetCacheState.asset_id.in_([path_to_asset[p] for p in chunk]))
|
||||||
|
)
|
||||||
|
winners_by_path.update(result.scalars().all())
|
||||||
|
|
||||||
all_paths_set = set(path_list)
|
all_paths_set = set(path_list)
|
||||||
losers_by_path = all_paths_set - winners_by_path
|
losers_by_path = all_paths_set - winners_by_path
|
||||||
@ -112,16 +121,23 @@ def seed_from_paths_batch(
|
|||||||
return {"inserted_infos": 0, "won_states": 0, "lost_states": len(losers_by_path)}
|
return {"inserted_infos": 0, "won_states": 0, "lost_states": len(losers_by_path)}
|
||||||
|
|
||||||
# insert AssetInfo only for winners
|
# insert AssetInfo only for winners
|
||||||
|
# Insert with ON CONFLICT DO NOTHING, then query to find which were actually inserted
|
||||||
winner_info_rows = [asset_to_info[path_to_asset[p]] for p in winners_by_path]
|
winner_info_rows = [asset_to_info[path_to_asset[p]] for p in winners_by_path]
|
||||||
ins_info = (
|
ins_info = (
|
||||||
sqlite.insert(AssetInfo)
|
sqlite.insert(AssetInfo)
|
||||||
.on_conflict_do_nothing(index_elements=[AssetInfo.asset_id, AssetInfo.owner_id, AssetInfo.name])
|
.on_conflict_do_nothing(index_elements=[AssetInfo.asset_id, AssetInfo.owner_id, AssetInfo.name])
|
||||||
.returning(AssetInfo.id)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
inserted_info_ids: set[str] = set()
|
|
||||||
for chunk in _iter_chunks(winner_info_rows, _rows_per_stmt(9)):
|
for chunk in _iter_chunks(winner_info_rows, _rows_per_stmt(9)):
|
||||||
inserted_info_ids.update((session.execute(ins_info, chunk)).scalars().all())
|
session.execute(ins_info, chunk)
|
||||||
|
|
||||||
|
# Query to find which info rows were actually inserted (by matching our generated IDs)
|
||||||
|
all_info_ids = [row["id"] for row in winner_info_rows]
|
||||||
|
inserted_info_ids: set[str] = set()
|
||||||
|
for chunk in _iter_chunks(all_info_ids, MAX_BIND_PARAMS):
|
||||||
|
result = session.execute(
|
||||||
|
sqlalchemy.select(AssetInfo.id).where(AssetInfo.id.in_(chunk))
|
||||||
|
)
|
||||||
|
inserted_info_ids.update(result.scalars().all())
|
||||||
|
|
||||||
# build and insert tag + meta rows for the AssetInfo
|
# build and insert tag + meta rows for the AssetInfo
|
||||||
tag_rows: list[dict] = []
|
tag_rows: list[dict] = []
|
||||||
|
|||||||
@ -10,6 +10,7 @@ import hashlib
|
|||||||
|
|
||||||
class Source:
|
class Source:
|
||||||
custom_node = "custom_node"
|
custom_node = "custom_node"
|
||||||
|
templates = "templates"
|
||||||
|
|
||||||
class SubgraphEntry(TypedDict):
|
class SubgraphEntry(TypedDict):
|
||||||
source: str
|
source: str
|
||||||
@ -38,6 +39,18 @@ class CustomNodeSubgraphEntryInfo(TypedDict):
|
|||||||
class SubgraphManager:
|
class SubgraphManager:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.cached_custom_node_subgraphs: dict[SubgraphEntry] | None = None
|
self.cached_custom_node_subgraphs: dict[SubgraphEntry] | None = None
|
||||||
|
self.cached_blueprint_subgraphs: dict[SubgraphEntry] | None = None
|
||||||
|
|
||||||
|
def _create_entry(self, file: str, source: str, node_pack: str) -> tuple[str, SubgraphEntry]:
|
||||||
|
"""Create a subgraph entry from a file path. Expects normalized path (forward slashes)."""
|
||||||
|
entry_id = hashlib.sha256(f"{source}{file}".encode()).hexdigest()
|
||||||
|
entry: SubgraphEntry = {
|
||||||
|
"source": source,
|
||||||
|
"name": os.path.splitext(os.path.basename(file))[0],
|
||||||
|
"path": file,
|
||||||
|
"info": {"node_pack": node_pack},
|
||||||
|
}
|
||||||
|
return entry_id, entry
|
||||||
|
|
||||||
async def load_entry_data(self, entry: SubgraphEntry):
|
async def load_entry_data(self, entry: SubgraphEntry):
|
||||||
with open(entry['path'], 'r') as f:
|
with open(entry['path'], 'r') as f:
|
||||||
@ -60,53 +73,60 @@ class SubgraphManager:
|
|||||||
return entries
|
return entries
|
||||||
|
|
||||||
async def get_custom_node_subgraphs(self, loadedModules, force_reload=False):
|
async def get_custom_node_subgraphs(self, loadedModules, force_reload=False):
|
||||||
# if not forced to reload and cached, return cache
|
"""Load subgraphs from custom nodes."""
|
||||||
if not force_reload and self.cached_custom_node_subgraphs is not None:
|
if not force_reload and self.cached_custom_node_subgraphs is not None:
|
||||||
return self.cached_custom_node_subgraphs
|
return self.cached_custom_node_subgraphs
|
||||||
# Load subgraphs from custom nodes
|
|
||||||
subfolder = "subgraphs"
|
|
||||||
subgraphs_dict: dict[SubgraphEntry] = {}
|
|
||||||
|
|
||||||
|
subgraphs_dict: dict[SubgraphEntry] = {}
|
||||||
for folder in folder_paths.get_folder_paths("custom_nodes"):
|
for folder in folder_paths.get_folder_paths("custom_nodes"):
|
||||||
pattern = os.path.join(folder, f"*/{subfolder}/*.json")
|
pattern = os.path.join(folder, "*/subgraphs/*.json")
|
||||||
matched_files = glob.glob(pattern)
|
for file in glob.glob(pattern):
|
||||||
for file in matched_files:
|
|
||||||
# replace backslashes with forward slashes
|
|
||||||
file = file.replace('\\', '/')
|
file = file.replace('\\', '/')
|
||||||
info: CustomNodeSubgraphEntryInfo = {
|
node_pack = "custom_nodes." + file.split('/')[-3]
|
||||||
"node_pack": "custom_nodes." + file.split('/')[-3]
|
entry_id, entry = self._create_entry(file, Source.custom_node, node_pack)
|
||||||
}
|
subgraphs_dict[entry_id] = entry
|
||||||
source = Source.custom_node
|
|
||||||
# hash source + path to make sure id will be as unique as possible, but
|
|
||||||
# reproducible across backend reloads
|
|
||||||
id = hashlib.sha256(f"{source}{file}".encode()).hexdigest()
|
|
||||||
entry: SubgraphEntry = {
|
|
||||||
"source": Source.custom_node,
|
|
||||||
"name": os.path.splitext(os.path.basename(file))[0],
|
|
||||||
"path": file,
|
|
||||||
"info": info,
|
|
||||||
}
|
|
||||||
subgraphs_dict[id] = entry
|
|
||||||
self.cached_custom_node_subgraphs = subgraphs_dict
|
self.cached_custom_node_subgraphs = subgraphs_dict
|
||||||
return subgraphs_dict
|
return subgraphs_dict
|
||||||
|
|
||||||
async def get_custom_node_subgraph(self, id: str, loadedModules):
|
async def get_blueprint_subgraphs(self, force_reload=False):
|
||||||
subgraphs = await self.get_custom_node_subgraphs(loadedModules)
|
"""Load subgraphs from the blueprints directory."""
|
||||||
entry: SubgraphEntry = subgraphs.get(id, None)
|
if not force_reload and self.cached_blueprint_subgraphs is not None:
|
||||||
if entry is not None and entry.get('data', None) is None:
|
return self.cached_blueprint_subgraphs
|
||||||
|
|
||||||
|
subgraphs_dict: dict[SubgraphEntry] = {}
|
||||||
|
blueprints_dir = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'blueprints')
|
||||||
|
|
||||||
|
if os.path.exists(blueprints_dir):
|
||||||
|
for file in glob.glob(os.path.join(blueprints_dir, "*.json")):
|
||||||
|
file = file.replace('\\', '/')
|
||||||
|
entry_id, entry = self._create_entry(file, Source.templates, "comfyui")
|
||||||
|
subgraphs_dict[entry_id] = entry
|
||||||
|
|
||||||
|
self.cached_blueprint_subgraphs = subgraphs_dict
|
||||||
|
return subgraphs_dict
|
||||||
|
|
||||||
|
async def get_all_subgraphs(self, loadedModules, force_reload=False):
|
||||||
|
"""Get all subgraphs from all sources (custom nodes and blueprints)."""
|
||||||
|
custom_node_subgraphs = await self.get_custom_node_subgraphs(loadedModules, force_reload)
|
||||||
|
blueprint_subgraphs = await self.get_blueprint_subgraphs(force_reload)
|
||||||
|
return {**custom_node_subgraphs, **blueprint_subgraphs}
|
||||||
|
|
||||||
|
async def get_subgraph(self, id: str, loadedModules):
|
||||||
|
"""Get a specific subgraph by ID from any source."""
|
||||||
|
entry = (await self.get_all_subgraphs(loadedModules)).get(id)
|
||||||
|
if entry is not None and entry.get('data') is None:
|
||||||
await self.load_entry_data(entry)
|
await self.load_entry_data(entry)
|
||||||
return entry
|
return entry
|
||||||
|
|
||||||
def add_routes(self, routes, loadedModules):
|
def add_routes(self, routes, loadedModules):
|
||||||
@routes.get("/global_subgraphs")
|
@routes.get("/global_subgraphs")
|
||||||
async def get_global_subgraphs(request):
|
async def get_global_subgraphs(request):
|
||||||
subgraphs_dict = await self.get_custom_node_subgraphs(loadedModules)
|
subgraphs_dict = await self.get_all_subgraphs(loadedModules)
|
||||||
# NOTE: we may want to include other sources of global subgraphs such as templates in the future;
|
|
||||||
# that's the reasoning for the current implementation
|
|
||||||
return web.json_response(await self.sanitize_entries(subgraphs_dict, remove_data=True))
|
return web.json_response(await self.sanitize_entries(subgraphs_dict, remove_data=True))
|
||||||
|
|
||||||
@routes.get("/global_subgraphs/{id}")
|
@routes.get("/global_subgraphs/{id}")
|
||||||
async def get_global_subgraph(request):
|
async def get_global_subgraph(request):
|
||||||
id = request.match_info.get("id", None)
|
id = request.match_info.get("id", None)
|
||||||
subgraph = await self.get_custom_node_subgraph(id, loadedModules)
|
subgraph = await self.get_subgraph(id, loadedModules)
|
||||||
return web.json_response(await self.sanitize_entry(subgraph))
|
return web.json_response(await self.sanitize_entry(subgraph))
|
||||||
|
|||||||
0
blueprints/put_blueprints_here
Normal file
0
blueprints/put_blueprints_here
Normal file
106
comfy/float.py
106
comfy/float.py
@ -65,3 +65,109 @@ def stochastic_rounding(value, dtype, seed=0):
|
|||||||
return output
|
return output
|
||||||
|
|
||||||
return value.to(dtype=dtype)
|
return value.to(dtype=dtype)
|
||||||
|
|
||||||
|
|
||||||
|
# TODO: improve this?
|
||||||
|
def stochastic_float_to_fp4_e2m1(x, generator):
|
||||||
|
orig_shape = x.shape
|
||||||
|
sign = torch.signbit(x).to(torch.uint8)
|
||||||
|
|
||||||
|
exp = torch.floor(torch.log2(x.abs()) + 1.0).clamp(0, 3)
|
||||||
|
x += (torch.rand(x.size(), dtype=x.dtype, layout=x.layout, device=x.device, generator=generator) - 0.5) * (2 ** (exp - 2.0)) * 1.25
|
||||||
|
|
||||||
|
x = x.abs()
|
||||||
|
exp = torch.floor(torch.log2(x) + 1.1925).clamp(0, 3)
|
||||||
|
|
||||||
|
mantissa = torch.where(
|
||||||
|
exp > 0,
|
||||||
|
(x / (2.0 ** (exp - 1)) - 1.0) * 2.0,
|
||||||
|
(x * 2.0),
|
||||||
|
out=x
|
||||||
|
).round().to(torch.uint8)
|
||||||
|
del x
|
||||||
|
|
||||||
|
exp = exp.to(torch.uint8)
|
||||||
|
|
||||||
|
fp4 = (sign << 3) | (exp << 1) | mantissa
|
||||||
|
del sign, exp, mantissa
|
||||||
|
|
||||||
|
fp4_flat = fp4.view(-1)
|
||||||
|
packed = (fp4_flat[0::2] << 4) | fp4_flat[1::2]
|
||||||
|
return packed.reshape(list(orig_shape)[:-1] + [-1])
|
||||||
|
|
||||||
|
|
||||||
|
def to_blocked(input_matrix, flatten: bool = True) -> torch.Tensor:
|
||||||
|
"""
|
||||||
|
Rearrange a large matrix by breaking it into blocks and applying the rearrangement pattern.
|
||||||
|
See:
|
||||||
|
https://docs.nvidia.com/cuda/cublas/index.html#d-block-scaling-factors-layout
|
||||||
|
|
||||||
|
Args:
|
||||||
|
input_matrix: Input tensor of shape (H, W)
|
||||||
|
Returns:
|
||||||
|
Rearranged tensor of shape (32*ceil_div(H,128), 16*ceil_div(W,4))
|
||||||
|
"""
|
||||||
|
|
||||||
|
def ceil_div(a, b):
|
||||||
|
return (a + b - 1) // b
|
||||||
|
|
||||||
|
rows, cols = input_matrix.shape
|
||||||
|
n_row_blocks = ceil_div(rows, 128)
|
||||||
|
n_col_blocks = ceil_div(cols, 4)
|
||||||
|
|
||||||
|
# Calculate the padded shape
|
||||||
|
padded_rows = n_row_blocks * 128
|
||||||
|
padded_cols = n_col_blocks * 4
|
||||||
|
|
||||||
|
padded = input_matrix
|
||||||
|
if (rows, cols) != (padded_rows, padded_cols):
|
||||||
|
padded = torch.zeros(
|
||||||
|
(padded_rows, padded_cols),
|
||||||
|
device=input_matrix.device,
|
||||||
|
dtype=input_matrix.dtype,
|
||||||
|
)
|
||||||
|
padded[:rows, :cols] = input_matrix
|
||||||
|
|
||||||
|
# Rearrange the blocks
|
||||||
|
blocks = padded.view(n_row_blocks, 128, n_col_blocks, 4).permute(0, 2, 1, 3)
|
||||||
|
rearranged = blocks.reshape(-1, 4, 32, 4).transpose(1, 2).reshape(-1, 32, 16)
|
||||||
|
if flatten:
|
||||||
|
return rearranged.flatten()
|
||||||
|
|
||||||
|
return rearranged.reshape(padded_rows, padded_cols)
|
||||||
|
|
||||||
|
|
||||||
|
def stochastic_round_quantize_nvfp4(x, per_tensor_scale, pad_16x, seed=0):
|
||||||
|
F4_E2M1_MAX = 6.0
|
||||||
|
F8_E4M3_MAX = 448.0
|
||||||
|
|
||||||
|
def roundup(x: int, multiple: int) -> int:
|
||||||
|
"""Round up x to the nearest multiple."""
|
||||||
|
return ((x + multiple - 1) // multiple) * multiple
|
||||||
|
|
||||||
|
orig_shape = x.shape
|
||||||
|
|
||||||
|
# Handle padding
|
||||||
|
if pad_16x:
|
||||||
|
rows, cols = x.shape
|
||||||
|
padded_rows = roundup(rows, 16)
|
||||||
|
padded_cols = roundup(cols, 16)
|
||||||
|
if padded_rows != rows or padded_cols != cols:
|
||||||
|
x = torch.nn.functional.pad(x, (0, padded_cols - cols, 0, padded_rows - rows))
|
||||||
|
# Note: We update orig_shape because the output tensor logic below assumes x.shape matches
|
||||||
|
# what we want to produce. If we pad here, we want the padded output.
|
||||||
|
orig_shape = x.shape
|
||||||
|
|
||||||
|
block_size = 16
|
||||||
|
|
||||||
|
x = x.reshape(orig_shape[0], -1, block_size)
|
||||||
|
scaled_block_scales_fp8 = torch.clamp(((torch.amax(torch.abs(x), dim=-1)) / F4_E2M1_MAX) / per_tensor_scale.to(x.dtype), max=F8_E4M3_MAX).to(torch.float8_e4m3fn)
|
||||||
|
x /= (per_tensor_scale.to(x.dtype) * scaled_block_scales_fp8.to(x.dtype)).unsqueeze(-1)
|
||||||
|
|
||||||
|
generator = torch.Generator(device=x.device)
|
||||||
|
generator.manual_seed(seed)
|
||||||
|
|
||||||
|
x = x.view(orig_shape).nan_to_num()
|
||||||
|
data_lp = stochastic_float_to_fp4_e2m1(x, generator=generator)
|
||||||
|
blocked_scales = to_blocked(scaled_block_scales_fp8, flatten=False)
|
||||||
|
return data_lp, blocked_scales
|
||||||
|
|||||||
@ -699,7 +699,7 @@ def mixed_precision_ops(quant_config={}, compute_dtype=torch.bfloat16, full_prec
|
|||||||
def set_weight(self, weight, inplace_update=False, seed=None, return_weight=False, **kwargs):
|
def set_weight(self, weight, inplace_update=False, seed=None, return_weight=False, **kwargs):
|
||||||
if getattr(self, 'layout_type', None) is not None:
|
if getattr(self, 'layout_type', None) is not None:
|
||||||
# dtype is now implicit in the layout class
|
# dtype is now implicit in the layout class
|
||||||
weight = QuantizedTensor.from_float(weight, self.layout_type, scale="recalculate", stochastic_rounding=seed, inplace_ops=True)
|
weight = QuantizedTensor.from_float(weight, self.layout_type, scale="recalculate", stochastic_rounding=seed, inplace_ops=True).to(self.weight.dtype)
|
||||||
else:
|
else:
|
||||||
weight = weight.to(self.weight.dtype)
|
weight = weight.to(self.weight.dtype)
|
||||||
if return_weight:
|
if return_weight:
|
||||||
|
|||||||
@ -7,7 +7,7 @@ try:
|
|||||||
QuantizedTensor,
|
QuantizedTensor,
|
||||||
QuantizedLayout,
|
QuantizedLayout,
|
||||||
TensorCoreFP8Layout as _CKFp8Layout,
|
TensorCoreFP8Layout as _CKFp8Layout,
|
||||||
TensorCoreNVFP4Layout, # Direct import, no wrapper needed
|
TensorCoreNVFP4Layout as _CKNvfp4Layout,
|
||||||
register_layout_op,
|
register_layout_op,
|
||||||
register_layout_class,
|
register_layout_class,
|
||||||
get_layout_class,
|
get_layout_class,
|
||||||
@ -34,7 +34,7 @@ except ImportError as e:
|
|||||||
class _CKFp8Layout:
|
class _CKFp8Layout:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
class TensorCoreNVFP4Layout:
|
class _CKNvfp4Layout:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def register_layout_class(name, cls):
|
def register_layout_class(name, cls):
|
||||||
@ -84,6 +84,39 @@ class _TensorCoreFP8LayoutBase(_CKFp8Layout):
|
|||||||
return qdata, params
|
return qdata, params
|
||||||
|
|
||||||
|
|
||||||
|
class TensorCoreNVFP4Layout(_CKNvfp4Layout):
|
||||||
|
@classmethod
|
||||||
|
def quantize(cls, tensor, scale=None, stochastic_rounding=0, inplace_ops=False):
|
||||||
|
if tensor.dim() != 2:
|
||||||
|
raise ValueError(f"NVFP4 requires 2D tensor, got {tensor.dim()}D")
|
||||||
|
|
||||||
|
orig_dtype = tensor.dtype
|
||||||
|
orig_shape = tuple(tensor.shape)
|
||||||
|
|
||||||
|
if scale is None or (isinstance(scale, str) and scale == "recalculate"):
|
||||||
|
scale = torch.amax(tensor.abs()) / (ck.float_utils.F8_E4M3_MAX * ck.float_utils.F4_E2M1_MAX)
|
||||||
|
|
||||||
|
if not isinstance(scale, torch.Tensor):
|
||||||
|
scale = torch.tensor(scale)
|
||||||
|
scale = scale.to(device=tensor.device, dtype=torch.float32)
|
||||||
|
|
||||||
|
padded_shape = cls.get_padded_shape(orig_shape)
|
||||||
|
needs_padding = padded_shape != orig_shape
|
||||||
|
|
||||||
|
if stochastic_rounding > 0:
|
||||||
|
qdata, block_scale = comfy.float.stochastic_round_quantize_nvfp4(tensor, scale, pad_16x=needs_padding, seed=stochastic_rounding)
|
||||||
|
else:
|
||||||
|
qdata, block_scale = ck.quantize_nvfp4(tensor, scale, pad_16x=needs_padding)
|
||||||
|
|
||||||
|
params = cls.Params(
|
||||||
|
scale=scale,
|
||||||
|
orig_dtype=orig_dtype,
|
||||||
|
orig_shape=orig_shape,
|
||||||
|
block_scale=block_scale,
|
||||||
|
)
|
||||||
|
return qdata, params
|
||||||
|
|
||||||
|
|
||||||
class TensorCoreFP8E4M3Layout(_TensorCoreFP8LayoutBase):
|
class TensorCoreFP8E4M3Layout(_TensorCoreFP8LayoutBase):
|
||||||
FP8_DTYPE = torch.float8_e4m3fn
|
FP8_DTYPE = torch.float8_e4m3fn
|
||||||
|
|
||||||
|
|||||||
@ -845,7 +845,7 @@ class LTXAV(LTXV):
|
|||||||
|
|
||||||
def __init__(self, unet_config):
|
def __init__(self, unet_config):
|
||||||
super().__init__(unet_config)
|
super().__init__(unet_config)
|
||||||
self.memory_usage_factor = 0.061 # TODO
|
self.memory_usage_factor = 0.077 # TODO
|
||||||
|
|
||||||
def get_model(self, state_dict, prefix="", device=None):
|
def get_model(self, state_dict, prefix="", device=None):
|
||||||
out = model_base.LTXAV(self, device=device)
|
out = model_base.LTXAV(self, device=device)
|
||||||
|
|||||||
@ -1225,6 +1225,7 @@ class NodeInfoV1:
|
|||||||
deprecated: bool=None
|
deprecated: bool=None
|
||||||
experimental: bool=None
|
experimental: bool=None
|
||||||
api_node: bool=None
|
api_node: bool=None
|
||||||
|
price_badge: dict | None = None
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class NodeInfoV3:
|
class NodeInfoV3:
|
||||||
@ -1234,11 +1235,77 @@ class NodeInfoV3:
|
|||||||
name: str=None
|
name: str=None
|
||||||
display_name: str=None
|
display_name: str=None
|
||||||
description: str=None
|
description: str=None
|
||||||
|
python_module: Any = None
|
||||||
category: str=None
|
category: str=None
|
||||||
output_node: bool=None
|
output_node: bool=None
|
||||||
deprecated: bool=None
|
deprecated: bool=None
|
||||||
experimental: bool=None
|
experimental: bool=None
|
||||||
api_node: bool=None
|
api_node: bool=None
|
||||||
|
price_badge: dict | None = None
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class PriceBadgeDepends:
|
||||||
|
widgets: list[str] = field(default_factory=list)
|
||||||
|
inputs: list[str] = field(default_factory=list)
|
||||||
|
input_groups: list[str] = field(default_factory=list)
|
||||||
|
|
||||||
|
def validate(self) -> None:
|
||||||
|
if not isinstance(self.widgets, list) or any(not isinstance(x, str) for x in self.widgets):
|
||||||
|
raise ValueError("PriceBadgeDepends.widgets must be a list[str].")
|
||||||
|
if not isinstance(self.inputs, list) or any(not isinstance(x, str) for x in self.inputs):
|
||||||
|
raise ValueError("PriceBadgeDepends.inputs must be a list[str].")
|
||||||
|
if not isinstance(self.input_groups, list) or any(not isinstance(x, str) for x in self.input_groups):
|
||||||
|
raise ValueError("PriceBadgeDepends.input_groups must be a list[str].")
|
||||||
|
|
||||||
|
def as_dict(self, schema_inputs: list["Input"]) -> dict[str, Any]:
|
||||||
|
# Build lookup: widget_id -> io_type
|
||||||
|
input_types: dict[str, str] = {}
|
||||||
|
for inp in schema_inputs:
|
||||||
|
all_inputs = inp.get_all()
|
||||||
|
input_types[inp.id] = inp.get_io_type() # First input is always the parent itself
|
||||||
|
for nested_inp in all_inputs[1:]:
|
||||||
|
# For DynamicCombo/DynamicSlot, nested inputs are prefixed with parent ID
|
||||||
|
# to match frontend naming convention (e.g., "should_texture.enable_pbr")
|
||||||
|
prefixed_id = f"{inp.id}.{nested_inp.id}"
|
||||||
|
input_types[prefixed_id] = nested_inp.get_io_type()
|
||||||
|
|
||||||
|
# Enrich widgets with type information, raising error for unknown widgets
|
||||||
|
widgets_data: list[dict[str, str]] = []
|
||||||
|
for w in self.widgets:
|
||||||
|
if w not in input_types:
|
||||||
|
raise ValueError(
|
||||||
|
f"PriceBadge depends_on.widgets references unknown widget '{w}'. "
|
||||||
|
f"Available widgets: {list(input_types.keys())}"
|
||||||
|
)
|
||||||
|
widgets_data.append({"name": w, "type": input_types[w]})
|
||||||
|
|
||||||
|
return {
|
||||||
|
"widgets": widgets_data,
|
||||||
|
"inputs": self.inputs,
|
||||||
|
"input_groups": self.input_groups,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class PriceBadge:
|
||||||
|
expr: str
|
||||||
|
depends_on: PriceBadgeDepends = field(default_factory=PriceBadgeDepends)
|
||||||
|
engine: str = field(default="jsonata")
|
||||||
|
|
||||||
|
def validate(self) -> None:
|
||||||
|
if self.engine != "jsonata":
|
||||||
|
raise ValueError(f"Unsupported PriceBadge.engine '{self.engine}'. Only 'jsonata' is supported.")
|
||||||
|
if not isinstance(self.expr, str) or not self.expr.strip():
|
||||||
|
raise ValueError("PriceBadge.expr must be a non-empty string.")
|
||||||
|
self.depends_on.validate()
|
||||||
|
|
||||||
|
def as_dict(self, schema_inputs: list["Input"]) -> dict[str, Any]:
|
||||||
|
return {
|
||||||
|
"engine": self.engine,
|
||||||
|
"depends_on": self.depends_on.as_dict(schema_inputs),
|
||||||
|
"expr": self.expr,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
@ -1284,6 +1351,8 @@ class Schema:
|
|||||||
"""Flags a node as experimental, informing users that it may change or not work as expected."""
|
"""Flags a node as experimental, informing users that it may change or not work as expected."""
|
||||||
is_api_node: bool=False
|
is_api_node: bool=False
|
||||||
"""Flags a node as an API node. See: https://docs.comfy.org/tutorials/api-nodes/overview."""
|
"""Flags a node as an API node. See: https://docs.comfy.org/tutorials/api-nodes/overview."""
|
||||||
|
price_badge: PriceBadge | None = None
|
||||||
|
"""Optional client-evaluated pricing badge declaration for this node."""
|
||||||
not_idempotent: bool=False
|
not_idempotent: bool=False
|
||||||
"""Flags a node as not idempotent; when True, the node will run and not reuse the cached outputs when identical inputs are provided on a different node in the graph."""
|
"""Flags a node as not idempotent; when True, the node will run and not reuse the cached outputs when identical inputs are provided on a different node in the graph."""
|
||||||
enable_expand: bool=False
|
enable_expand: bool=False
|
||||||
@ -1314,6 +1383,8 @@ class Schema:
|
|||||||
input.validate()
|
input.validate()
|
||||||
for output in self.outputs:
|
for output in self.outputs:
|
||||||
output.validate()
|
output.validate()
|
||||||
|
if self.price_badge is not None:
|
||||||
|
self.price_badge.validate()
|
||||||
|
|
||||||
def finalize(self):
|
def finalize(self):
|
||||||
"""Add hidden based on selected schema options, and give outputs without ids default ids."""
|
"""Add hidden based on selected schema options, and give outputs without ids default ids."""
|
||||||
@ -1387,7 +1458,8 @@ class Schema:
|
|||||||
deprecated=self.is_deprecated,
|
deprecated=self.is_deprecated,
|
||||||
experimental=self.is_experimental,
|
experimental=self.is_experimental,
|
||||||
api_node=self.is_api_node,
|
api_node=self.is_api_node,
|
||||||
python_module=getattr(cls, "RELATIVE_PYTHON_MODULE", "nodes")
|
python_module=getattr(cls, "RELATIVE_PYTHON_MODULE", "nodes"),
|
||||||
|
price_badge=self.price_badge.as_dict(self.inputs) if self.price_badge is not None else None,
|
||||||
)
|
)
|
||||||
return info
|
return info
|
||||||
|
|
||||||
@ -1419,7 +1491,8 @@ class Schema:
|
|||||||
deprecated=self.is_deprecated,
|
deprecated=self.is_deprecated,
|
||||||
experimental=self.is_experimental,
|
experimental=self.is_experimental,
|
||||||
api_node=self.is_api_node,
|
api_node=self.is_api_node,
|
||||||
python_module=getattr(cls, "RELATIVE_PYTHON_MODULE", "nodes")
|
python_module=getattr(cls, "RELATIVE_PYTHON_MODULE", "nodes"),
|
||||||
|
price_badge=self.price_badge.as_dict(self.inputs) if self.price_badge is not None else None,
|
||||||
)
|
)
|
||||||
return info
|
return info
|
||||||
|
|
||||||
@ -1971,4 +2044,6 @@ __all__ = [
|
|||||||
"add_to_dict_v3",
|
"add_to_dict_v3",
|
||||||
"V3Data",
|
"V3Data",
|
||||||
"ImageCompare",
|
"ImageCompare",
|
||||||
|
"PriceBadgeDepends",
|
||||||
|
"PriceBadge",
|
||||||
]
|
]
|
||||||
|
|||||||
@ -97,6 +97,9 @@ class FluxProUltraImageNode(IO.ComfyNode):
|
|||||||
IO.Hidden.unique_id,
|
IO.Hidden.unique_id,
|
||||||
],
|
],
|
||||||
is_api_node=True,
|
is_api_node=True,
|
||||||
|
price_badge=IO.PriceBadge(
|
||||||
|
expr="""{"type":"usd","usd":0.06}""",
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -352,6 +355,9 @@ class FluxProExpandNode(IO.ComfyNode):
|
|||||||
IO.Hidden.unique_id,
|
IO.Hidden.unique_id,
|
||||||
],
|
],
|
||||||
is_api_node=True,
|
is_api_node=True,
|
||||||
|
price_badge=IO.PriceBadge(
|
||||||
|
expr="""{"type":"usd","usd":0.05}""",
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -458,6 +464,9 @@ class FluxProFillNode(IO.ComfyNode):
|
|||||||
IO.Hidden.unique_id,
|
IO.Hidden.unique_id,
|
||||||
],
|
],
|
||||||
is_api_node=True,
|
is_api_node=True,
|
||||||
|
price_badge=IO.PriceBadge(
|
||||||
|
expr="""{"type":"usd","usd":0.05}""",
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -511,6 +520,21 @@ class Flux2ProImageNode(IO.ComfyNode):
|
|||||||
NODE_ID = "Flux2ProImageNode"
|
NODE_ID = "Flux2ProImageNode"
|
||||||
DISPLAY_NAME = "Flux.2 [pro] Image"
|
DISPLAY_NAME = "Flux.2 [pro] Image"
|
||||||
API_ENDPOINT = "/proxy/bfl/flux-2-pro/generate"
|
API_ENDPOINT = "/proxy/bfl/flux-2-pro/generate"
|
||||||
|
PRICE_BADGE_EXPR = """
|
||||||
|
(
|
||||||
|
$MP := 1024 * 1024;
|
||||||
|
$outMP := $max([1, $floor(((widgets.width * widgets.height) + $MP - 1) / $MP)]);
|
||||||
|
$outputCost := 0.03 + 0.015 * ($outMP - 1);
|
||||||
|
inputs.images.connected
|
||||||
|
? {
|
||||||
|
"type":"range_usd",
|
||||||
|
"min_usd": $outputCost + 0.015,
|
||||||
|
"max_usd": $outputCost + 0.12,
|
||||||
|
"format": { "approximate": true }
|
||||||
|
}
|
||||||
|
: {"type":"usd","usd": $outputCost}
|
||||||
|
)
|
||||||
|
"""
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def define_schema(cls) -> IO.Schema:
|
def define_schema(cls) -> IO.Schema:
|
||||||
@ -563,6 +587,10 @@ class Flux2ProImageNode(IO.ComfyNode):
|
|||||||
IO.Hidden.unique_id,
|
IO.Hidden.unique_id,
|
||||||
],
|
],
|
||||||
is_api_node=True,
|
is_api_node=True,
|
||||||
|
price_badge=IO.PriceBadge(
|
||||||
|
depends_on=IO.PriceBadgeDepends(widgets=["width", "height"], inputs=["images"]),
|
||||||
|
expr=cls.PRICE_BADGE_EXPR,
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -623,6 +651,22 @@ class Flux2MaxImageNode(Flux2ProImageNode):
|
|||||||
NODE_ID = "Flux2MaxImageNode"
|
NODE_ID = "Flux2MaxImageNode"
|
||||||
DISPLAY_NAME = "Flux.2 [max] Image"
|
DISPLAY_NAME = "Flux.2 [max] Image"
|
||||||
API_ENDPOINT = "/proxy/bfl/flux-2-max/generate"
|
API_ENDPOINT = "/proxy/bfl/flux-2-max/generate"
|
||||||
|
PRICE_BADGE_EXPR = """
|
||||||
|
(
|
||||||
|
$MP := 1024 * 1024;
|
||||||
|
$outMP := $max([1, $floor(((widgets.width * widgets.height) + $MP - 1) / $MP)]);
|
||||||
|
$outputCost := 0.07 + 0.03 * ($outMP - 1);
|
||||||
|
|
||||||
|
inputs.images.connected
|
||||||
|
? {
|
||||||
|
"type":"range_usd",
|
||||||
|
"min_usd": $outputCost + 0.03,
|
||||||
|
"max_usd": $outputCost + 0.24,
|
||||||
|
"format": { "approximate": true }
|
||||||
|
}
|
||||||
|
: {"type":"usd","usd": $outputCost}
|
||||||
|
)
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
class BFLExtension(ComfyExtension):
|
class BFLExtension(ComfyExtension):
|
||||||
|
|||||||
@ -126,6 +126,9 @@ class ByteDanceImageNode(IO.ComfyNode):
|
|||||||
IO.Hidden.unique_id,
|
IO.Hidden.unique_id,
|
||||||
],
|
],
|
||||||
is_api_node=True,
|
is_api_node=True,
|
||||||
|
price_badge=IO.PriceBadge(
|
||||||
|
expr="""{"type":"usd","usd":0.03}""",
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -367,6 +370,19 @@ class ByteDanceSeedreamNode(IO.ComfyNode):
|
|||||||
IO.Hidden.unique_id,
|
IO.Hidden.unique_id,
|
||||||
],
|
],
|
||||||
is_api_node=True,
|
is_api_node=True,
|
||||||
|
price_badge=IO.PriceBadge(
|
||||||
|
depends_on=IO.PriceBadgeDepends(widgets=["model"]),
|
||||||
|
expr="""
|
||||||
|
(
|
||||||
|
$price := $contains(widgets.model, "seedream-4-5-251128") ? 0.04 : 0.03;
|
||||||
|
{
|
||||||
|
"type":"usd",
|
||||||
|
"usd": $price,
|
||||||
|
"format": { "suffix":" x images/Run", "approximate": true }
|
||||||
|
}
|
||||||
|
)
|
||||||
|
""",
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -522,6 +538,7 @@ class ByteDanceTextToVideoNode(IO.ComfyNode):
|
|||||||
IO.Hidden.unique_id,
|
IO.Hidden.unique_id,
|
||||||
],
|
],
|
||||||
is_api_node=True,
|
is_api_node=True,
|
||||||
|
price_badge=PRICE_BADGE_VIDEO,
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -632,6 +649,7 @@ class ByteDanceImageToVideoNode(IO.ComfyNode):
|
|||||||
IO.Hidden.unique_id,
|
IO.Hidden.unique_id,
|
||||||
],
|
],
|
||||||
is_api_node=True,
|
is_api_node=True,
|
||||||
|
price_badge=PRICE_BADGE_VIDEO,
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -754,6 +772,7 @@ class ByteDanceFirstLastFrameNode(IO.ComfyNode):
|
|||||||
IO.Hidden.unique_id,
|
IO.Hidden.unique_id,
|
||||||
],
|
],
|
||||||
is_api_node=True,
|
is_api_node=True,
|
||||||
|
price_badge=PRICE_BADGE_VIDEO,
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -877,6 +896,7 @@ class ByteDanceImageReferenceNode(IO.ComfyNode):
|
|||||||
IO.Hidden.unique_id,
|
IO.Hidden.unique_id,
|
||||||
],
|
],
|
||||||
is_api_node=True,
|
is_api_node=True,
|
||||||
|
price_badge=PRICE_BADGE_VIDEO,
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -946,6 +966,52 @@ def raise_if_text_params(prompt: str, text_params: list[str]) -> None:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
PRICE_BADGE_VIDEO = IO.PriceBadge(
|
||||||
|
depends_on=IO.PriceBadgeDepends(widgets=["model", "duration", "resolution"]),
|
||||||
|
expr="""
|
||||||
|
(
|
||||||
|
$priceByModel := {
|
||||||
|
"seedance-1-0-pro": {
|
||||||
|
"480p":[0.23,0.24],
|
||||||
|
"720p":[0.51,0.56],
|
||||||
|
"1080p":[1.18,1.22]
|
||||||
|
},
|
||||||
|
"seedance-1-0-pro-fast": {
|
||||||
|
"480p":[0.09,0.1],
|
||||||
|
"720p":[0.21,0.23],
|
||||||
|
"1080p":[0.47,0.49]
|
||||||
|
},
|
||||||
|
"seedance-1-0-lite": {
|
||||||
|
"480p":[0.17,0.18],
|
||||||
|
"720p":[0.37,0.41],
|
||||||
|
"1080p":[0.85,0.88]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
$model := widgets.model;
|
||||||
|
$modelKey :=
|
||||||
|
$contains($model, "seedance-1-0-pro-fast") ? "seedance-1-0-pro-fast" :
|
||||||
|
$contains($model, "seedance-1-0-pro") ? "seedance-1-0-pro" :
|
||||||
|
"seedance-1-0-lite";
|
||||||
|
$resolution := widgets.resolution;
|
||||||
|
$resKey :=
|
||||||
|
$contains($resolution, "1080") ? "1080p" :
|
||||||
|
$contains($resolution, "720") ? "720p" :
|
||||||
|
"480p";
|
||||||
|
$modelPrices := $lookup($priceByModel, $modelKey);
|
||||||
|
$baseRange := $lookup($modelPrices, $resKey);
|
||||||
|
$min10s := $baseRange[0];
|
||||||
|
$max10s := $baseRange[1];
|
||||||
|
$scale := widgets.duration / 10;
|
||||||
|
$minCost := $min10s * $scale;
|
||||||
|
$maxCost := $max10s * $scale;
|
||||||
|
($minCost = $maxCost)
|
||||||
|
? {"type":"usd","usd": $minCost}
|
||||||
|
: {"type":"range_usd","min_usd": $minCost, "max_usd": $maxCost}
|
||||||
|
)
|
||||||
|
""",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class ByteDanceExtension(ComfyExtension):
|
class ByteDanceExtension(ComfyExtension):
|
||||||
@override
|
@override
|
||||||
async def get_node_list(self) -> list[type[IO.ComfyNode]]:
|
async def get_node_list(self) -> list[type[IO.ComfyNode]]:
|
||||||
|
|||||||
@ -130,7 +130,7 @@ def get_parts_by_type(response: GeminiGenerateContentResponse, part_type: Litera
|
|||||||
Returns:
|
Returns:
|
||||||
List of response parts matching the requested type.
|
List of response parts matching the requested type.
|
||||||
"""
|
"""
|
||||||
if response.candidates is None:
|
if not response.candidates:
|
||||||
if response.promptFeedback and response.promptFeedback.blockReason:
|
if response.promptFeedback and response.promptFeedback.blockReason:
|
||||||
feedback = response.promptFeedback
|
feedback = response.promptFeedback
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
@ -141,14 +141,24 @@ def get_parts_by_type(response: GeminiGenerateContentResponse, part_type: Litera
|
|||||||
"try changing it to `IMAGE+TEXT` to view the model's reasoning and understand why image generation failed."
|
"try changing it to `IMAGE+TEXT` to view the model's reasoning and understand why image generation failed."
|
||||||
)
|
)
|
||||||
parts = []
|
parts = []
|
||||||
for part in response.candidates[0].content.parts:
|
blocked_reasons = []
|
||||||
if part_type == "text" and part.text:
|
for candidate in response.candidates:
|
||||||
parts.append(part)
|
if candidate.finishReason and candidate.finishReason.upper() == "IMAGE_PROHIBITED_CONTENT":
|
||||||
elif part.inlineData and part.inlineData.mimeType == part_type:
|
blocked_reasons.append(candidate.finishReason)
|
||||||
parts.append(part)
|
continue
|
||||||
elif part.fileData and part.fileData.mimeType == part_type:
|
if candidate.content is None or candidate.content.parts is None:
|
||||||
parts.append(part)
|
continue
|
||||||
# Skip parts that don't match the requested type
|
for part in candidate.content.parts:
|
||||||
|
if part_type == "text" and part.text:
|
||||||
|
parts.append(part)
|
||||||
|
elif part.inlineData and part.inlineData.mimeType == part_type:
|
||||||
|
parts.append(part)
|
||||||
|
elif part.fileData and part.fileData.mimeType == part_type:
|
||||||
|
parts.append(part)
|
||||||
|
|
||||||
|
if not parts and blocked_reasons:
|
||||||
|
raise ValueError(f"Gemini API blocked the request. Reasons: {blocked_reasons}")
|
||||||
|
|
||||||
return parts
|
return parts
|
||||||
|
|
||||||
|
|
||||||
@ -309,6 +319,30 @@ class GeminiNode(IO.ComfyNode):
|
|||||||
IO.Hidden.unique_id,
|
IO.Hidden.unique_id,
|
||||||
],
|
],
|
||||||
is_api_node=True,
|
is_api_node=True,
|
||||||
|
price_badge=IO.PriceBadge(
|
||||||
|
depends_on=IO.PriceBadgeDepends(widgets=["model"]),
|
||||||
|
expr="""
|
||||||
|
(
|
||||||
|
$m := widgets.model;
|
||||||
|
$contains($m, "gemini-2.5-flash") ? {
|
||||||
|
"type": "list_usd",
|
||||||
|
"usd": [0.0003, 0.0025],
|
||||||
|
"format": { "approximate": true, "separator": "-", "suffix": " per 1K tokens"}
|
||||||
|
}
|
||||||
|
: $contains($m, "gemini-2.5-pro") ? {
|
||||||
|
"type": "list_usd",
|
||||||
|
"usd": [0.00125, 0.01],
|
||||||
|
"format": { "approximate": true, "separator": "-", "suffix": " per 1K tokens" }
|
||||||
|
}
|
||||||
|
: $contains($m, "gemini-3-pro-preview") ? {
|
||||||
|
"type": "list_usd",
|
||||||
|
"usd": [0.002, 0.012],
|
||||||
|
"format": { "approximate": true, "separator": "-", "suffix": " per 1K tokens" }
|
||||||
|
}
|
||||||
|
: {"type":"text", "text":"Token-based"}
|
||||||
|
)
|
||||||
|
""",
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -570,6 +604,9 @@ class GeminiImage(IO.ComfyNode):
|
|||||||
IO.Hidden.unique_id,
|
IO.Hidden.unique_id,
|
||||||
],
|
],
|
||||||
is_api_node=True,
|
is_api_node=True,
|
||||||
|
price_badge=IO.PriceBadge(
|
||||||
|
expr="""{"type":"usd","usd":0.039,"format":{"suffix":"/Image (1K)","approximate":true}}""",
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -700,6 +737,19 @@ class GeminiImage2(IO.ComfyNode):
|
|||||||
IO.Hidden.unique_id,
|
IO.Hidden.unique_id,
|
||||||
],
|
],
|
||||||
is_api_node=True,
|
is_api_node=True,
|
||||||
|
price_badge=IO.PriceBadge(
|
||||||
|
depends_on=IO.PriceBadgeDepends(widgets=["resolution"]),
|
||||||
|
expr="""
|
||||||
|
(
|
||||||
|
$r := widgets.resolution;
|
||||||
|
($contains($r,"1k") or $contains($r,"2k"))
|
||||||
|
? {"type":"usd","usd":0.134,"format":{"suffix":"/Image","approximate":true}}
|
||||||
|
: $contains($r,"4k")
|
||||||
|
? {"type":"usd","usd":0.24,"format":{"suffix":"/Image","approximate":true}}
|
||||||
|
: {"type":"text","text":"Token-based"}
|
||||||
|
)
|
||||||
|
""",
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
|||||||
@ -236,7 +236,6 @@ class IdeogramV1(IO.ComfyNode):
|
|||||||
display_name="Ideogram V1",
|
display_name="Ideogram V1",
|
||||||
category="api node/image/Ideogram",
|
category="api node/image/Ideogram",
|
||||||
description="Generates images using the Ideogram V1 model.",
|
description="Generates images using the Ideogram V1 model.",
|
||||||
is_api_node=True,
|
|
||||||
inputs=[
|
inputs=[
|
||||||
IO.String.Input(
|
IO.String.Input(
|
||||||
"prompt",
|
"prompt",
|
||||||
@ -298,6 +297,17 @@ class IdeogramV1(IO.ComfyNode):
|
|||||||
IO.Hidden.api_key_comfy_org,
|
IO.Hidden.api_key_comfy_org,
|
||||||
IO.Hidden.unique_id,
|
IO.Hidden.unique_id,
|
||||||
],
|
],
|
||||||
|
is_api_node=True,
|
||||||
|
price_badge=IO.PriceBadge(
|
||||||
|
depends_on=IO.PriceBadgeDepends(widgets=["num_images", "turbo"]),
|
||||||
|
expr="""
|
||||||
|
(
|
||||||
|
$n := widgets.num_images;
|
||||||
|
$base := (widgets.turbo = true) ? 0.0286 : 0.0858;
|
||||||
|
{"type":"usd","usd": $round($base * $n, 2)}
|
||||||
|
)
|
||||||
|
""",
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -351,7 +361,6 @@ class IdeogramV2(IO.ComfyNode):
|
|||||||
display_name="Ideogram V2",
|
display_name="Ideogram V2",
|
||||||
category="api node/image/Ideogram",
|
category="api node/image/Ideogram",
|
||||||
description="Generates images using the Ideogram V2 model.",
|
description="Generates images using the Ideogram V2 model.",
|
||||||
is_api_node=True,
|
|
||||||
inputs=[
|
inputs=[
|
||||||
IO.String.Input(
|
IO.String.Input(
|
||||||
"prompt",
|
"prompt",
|
||||||
@ -436,6 +445,17 @@ class IdeogramV2(IO.ComfyNode):
|
|||||||
IO.Hidden.api_key_comfy_org,
|
IO.Hidden.api_key_comfy_org,
|
||||||
IO.Hidden.unique_id,
|
IO.Hidden.unique_id,
|
||||||
],
|
],
|
||||||
|
is_api_node=True,
|
||||||
|
price_badge=IO.PriceBadge(
|
||||||
|
depends_on=IO.PriceBadgeDepends(widgets=["num_images", "turbo"]),
|
||||||
|
expr="""
|
||||||
|
(
|
||||||
|
$n := widgets.num_images;
|
||||||
|
$base := (widgets.turbo = true) ? 0.0715 : 0.1144;
|
||||||
|
{"type":"usd","usd": $round($base * $n, 2)}
|
||||||
|
)
|
||||||
|
""",
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -506,7 +526,6 @@ class IdeogramV3(IO.ComfyNode):
|
|||||||
category="api node/image/Ideogram",
|
category="api node/image/Ideogram",
|
||||||
description="Generates images using the Ideogram V3 model. "
|
description="Generates images using the Ideogram V3 model. "
|
||||||
"Supports both regular image generation from text prompts and image editing with mask.",
|
"Supports both regular image generation from text prompts and image editing with mask.",
|
||||||
is_api_node=True,
|
|
||||||
inputs=[
|
inputs=[
|
||||||
IO.String.Input(
|
IO.String.Input(
|
||||||
"prompt",
|
"prompt",
|
||||||
@ -591,6 +610,23 @@ class IdeogramV3(IO.ComfyNode):
|
|||||||
IO.Hidden.api_key_comfy_org,
|
IO.Hidden.api_key_comfy_org,
|
||||||
IO.Hidden.unique_id,
|
IO.Hidden.unique_id,
|
||||||
],
|
],
|
||||||
|
is_api_node=True,
|
||||||
|
price_badge=IO.PriceBadge(
|
||||||
|
depends_on=IO.PriceBadgeDepends(widgets=["rendering_speed", "num_images"], inputs=["character_image"]),
|
||||||
|
expr="""
|
||||||
|
(
|
||||||
|
$n := widgets.num_images;
|
||||||
|
$speed := widgets.rendering_speed;
|
||||||
|
$hasChar := inputs.character_image.connected;
|
||||||
|
$base :=
|
||||||
|
$contains($speed,"quality") ? ($hasChar ? 0.286 : 0.1287) :
|
||||||
|
$contains($speed,"default") ? ($hasChar ? 0.2145 : 0.0858) :
|
||||||
|
$contains($speed,"turbo") ? ($hasChar ? 0.143 : 0.0429) :
|
||||||
|
0.0858;
|
||||||
|
{"type":"usd","usd": $round($base * $n, 2)}
|
||||||
|
)
|
||||||
|
""",
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
|||||||
@ -764,6 +764,33 @@ class KlingTextToVideoNode(IO.ComfyNode):
|
|||||||
IO.Hidden.unique_id,
|
IO.Hidden.unique_id,
|
||||||
],
|
],
|
||||||
is_api_node=True,
|
is_api_node=True,
|
||||||
|
price_badge=IO.PriceBadge(
|
||||||
|
depends_on=IO.PriceBadgeDepends(widgets=["mode"]),
|
||||||
|
expr="""
|
||||||
|
(
|
||||||
|
$m := widgets.mode;
|
||||||
|
$contains($m,"v2-5-turbo")
|
||||||
|
? ($contains($m,"10") ? {"type":"usd","usd":0.7} : {"type":"usd","usd":0.35})
|
||||||
|
: $contains($m,"v2-1-master")
|
||||||
|
? ($contains($m,"10s") ? {"type":"usd","usd":2.8} : {"type":"usd","usd":1.4})
|
||||||
|
: $contains($m,"v2-master")
|
||||||
|
? ($contains($m,"10s") ? {"type":"usd","usd":2.8} : {"type":"usd","usd":1.4})
|
||||||
|
: $contains($m,"v1-6")
|
||||||
|
? (
|
||||||
|
$contains($m,"pro")
|
||||||
|
? ($contains($m,"10s") ? {"type":"usd","usd":0.98} : {"type":"usd","usd":0.49})
|
||||||
|
: ($contains($m,"10s") ? {"type":"usd","usd":0.56} : {"type":"usd","usd":0.28})
|
||||||
|
)
|
||||||
|
: $contains($m,"v1")
|
||||||
|
? (
|
||||||
|
$contains($m,"pro")
|
||||||
|
? ($contains($m,"10s") ? {"type":"usd","usd":0.98} : {"type":"usd","usd":0.49})
|
||||||
|
: ($contains($m,"10s") ? {"type":"usd","usd":0.28} : {"type":"usd","usd":0.14})
|
||||||
|
)
|
||||||
|
: {"type":"usd","usd":0.14}
|
||||||
|
)
|
||||||
|
""",
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -818,6 +845,16 @@ class OmniProTextToVideoNode(IO.ComfyNode):
|
|||||||
IO.Hidden.unique_id,
|
IO.Hidden.unique_id,
|
||||||
],
|
],
|
||||||
is_api_node=True,
|
is_api_node=True,
|
||||||
|
price_badge=IO.PriceBadge(
|
||||||
|
depends_on=IO.PriceBadgeDepends(widgets=["duration", "resolution"]),
|
||||||
|
expr="""
|
||||||
|
(
|
||||||
|
$mode := (widgets.resolution = "720p") ? "std" : "pro";
|
||||||
|
$rates := {"std": 0.084, "pro": 0.112};
|
||||||
|
{"type":"usd","usd": $lookup($rates, $mode) * widgets.duration}
|
||||||
|
)
|
||||||
|
""",
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -886,6 +923,16 @@ class OmniProFirstLastFrameNode(IO.ComfyNode):
|
|||||||
IO.Hidden.unique_id,
|
IO.Hidden.unique_id,
|
||||||
],
|
],
|
||||||
is_api_node=True,
|
is_api_node=True,
|
||||||
|
price_badge=IO.PriceBadge(
|
||||||
|
depends_on=IO.PriceBadgeDepends(widgets=["duration", "resolution"]),
|
||||||
|
expr="""
|
||||||
|
(
|
||||||
|
$mode := (widgets.resolution = "720p") ? "std" : "pro";
|
||||||
|
$rates := {"std": 0.084, "pro": 0.112};
|
||||||
|
{"type":"usd","usd": $lookup($rates, $mode) * widgets.duration}
|
||||||
|
)
|
||||||
|
""",
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -981,6 +1028,16 @@ class OmniProImageToVideoNode(IO.ComfyNode):
|
|||||||
IO.Hidden.unique_id,
|
IO.Hidden.unique_id,
|
||||||
],
|
],
|
||||||
is_api_node=True,
|
is_api_node=True,
|
||||||
|
price_badge=IO.PriceBadge(
|
||||||
|
depends_on=IO.PriceBadgeDepends(widgets=["duration", "resolution"]),
|
||||||
|
expr="""
|
||||||
|
(
|
||||||
|
$mode := (widgets.resolution = "720p") ? "std" : "pro";
|
||||||
|
$rates := {"std": 0.084, "pro": 0.112};
|
||||||
|
{"type":"usd","usd": $lookup($rates, $mode) * widgets.duration}
|
||||||
|
)
|
||||||
|
""",
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -1056,6 +1113,16 @@ class OmniProVideoToVideoNode(IO.ComfyNode):
|
|||||||
IO.Hidden.unique_id,
|
IO.Hidden.unique_id,
|
||||||
],
|
],
|
||||||
is_api_node=True,
|
is_api_node=True,
|
||||||
|
price_badge=IO.PriceBadge(
|
||||||
|
depends_on=IO.PriceBadgeDepends(widgets=["duration", "resolution"]),
|
||||||
|
expr="""
|
||||||
|
(
|
||||||
|
$mode := (widgets.resolution = "720p") ? "std" : "pro";
|
||||||
|
$rates := {"std": 0.126, "pro": 0.168};
|
||||||
|
{"type":"usd","usd": $lookup($rates, $mode) * widgets.duration}
|
||||||
|
)
|
||||||
|
""",
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -1142,6 +1209,16 @@ class OmniProEditVideoNode(IO.ComfyNode):
|
|||||||
IO.Hidden.unique_id,
|
IO.Hidden.unique_id,
|
||||||
],
|
],
|
||||||
is_api_node=True,
|
is_api_node=True,
|
||||||
|
price_badge=IO.PriceBadge(
|
||||||
|
depends_on=IO.PriceBadgeDepends(widgets=["resolution"]),
|
||||||
|
expr="""
|
||||||
|
(
|
||||||
|
$mode := (widgets.resolution = "720p") ? "std" : "pro";
|
||||||
|
$rates := {"std": 0.126, "pro": 0.168};
|
||||||
|
{"type":"usd","usd": $lookup($rates, $mode), "format":{"suffix":"/second"}}
|
||||||
|
)
|
||||||
|
""",
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -1228,6 +1305,9 @@ class OmniProImageNode(IO.ComfyNode):
|
|||||||
IO.Hidden.unique_id,
|
IO.Hidden.unique_id,
|
||||||
],
|
],
|
||||||
is_api_node=True,
|
is_api_node=True,
|
||||||
|
price_badge=IO.PriceBadge(
|
||||||
|
expr="""{"type":"usd","usd":0.028}""",
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -1313,6 +1393,9 @@ class KlingCameraControlT2VNode(IO.ComfyNode):
|
|||||||
IO.Hidden.unique_id,
|
IO.Hidden.unique_id,
|
||||||
],
|
],
|
||||||
is_api_node=True,
|
is_api_node=True,
|
||||||
|
price_badge=IO.PriceBadge(
|
||||||
|
expr="""{"type":"usd","usd":0.14}""",
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -1375,6 +1458,33 @@ class KlingImage2VideoNode(IO.ComfyNode):
|
|||||||
IO.Hidden.unique_id,
|
IO.Hidden.unique_id,
|
||||||
],
|
],
|
||||||
is_api_node=True,
|
is_api_node=True,
|
||||||
|
price_badge=IO.PriceBadge(
|
||||||
|
depends_on=IO.PriceBadgeDepends(widgets=["mode", "model_name", "duration"]),
|
||||||
|
expr="""
|
||||||
|
(
|
||||||
|
$mode := widgets.mode;
|
||||||
|
$model := widgets.model_name;
|
||||||
|
$dur := widgets.duration;
|
||||||
|
$contains($model,"v2-5-turbo")
|
||||||
|
? ($contains($dur,"10") ? {"type":"usd","usd":0.7} : {"type":"usd","usd":0.35})
|
||||||
|
: ($contains($model,"v2-1-master") or $contains($model,"v2-master"))
|
||||||
|
? ($contains($dur,"10") ? {"type":"usd","usd":2.8} : {"type":"usd","usd":1.4})
|
||||||
|
: ($contains($model,"v2-1") or $contains($model,"v1-6") or $contains($model,"v1-5"))
|
||||||
|
? (
|
||||||
|
$contains($mode,"pro")
|
||||||
|
? ($contains($dur,"10") ? {"type":"usd","usd":0.98} : {"type":"usd","usd":0.49})
|
||||||
|
: ($contains($dur,"10") ? {"type":"usd","usd":0.56} : {"type":"usd","usd":0.28})
|
||||||
|
)
|
||||||
|
: $contains($model,"v1")
|
||||||
|
? (
|
||||||
|
$contains($mode,"pro")
|
||||||
|
? ($contains($dur,"10") ? {"type":"usd","usd":0.98} : {"type":"usd","usd":0.49})
|
||||||
|
: ($contains($dur,"10") ? {"type":"usd","usd":0.28} : {"type":"usd","usd":0.14})
|
||||||
|
)
|
||||||
|
: {"type":"usd","usd":0.14}
|
||||||
|
)
|
||||||
|
""",
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -1448,6 +1558,9 @@ class KlingCameraControlI2VNode(IO.ComfyNode):
|
|||||||
IO.Hidden.unique_id,
|
IO.Hidden.unique_id,
|
||||||
],
|
],
|
||||||
is_api_node=True,
|
is_api_node=True,
|
||||||
|
price_badge=IO.PriceBadge(
|
||||||
|
expr="""{"type":"usd","usd":0.49}""",
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -1518,6 +1631,33 @@ class KlingStartEndFrameNode(IO.ComfyNode):
|
|||||||
IO.Hidden.unique_id,
|
IO.Hidden.unique_id,
|
||||||
],
|
],
|
||||||
is_api_node=True,
|
is_api_node=True,
|
||||||
|
price_badge=IO.PriceBadge(
|
||||||
|
depends_on=IO.PriceBadgeDepends(widgets=["mode"]),
|
||||||
|
expr="""
|
||||||
|
(
|
||||||
|
$m := widgets.mode;
|
||||||
|
$contains($m,"v2-5-turbo")
|
||||||
|
? ($contains($m,"10") ? {"type":"usd","usd":0.7} : {"type":"usd","usd":0.35})
|
||||||
|
: $contains($m,"v2-1")
|
||||||
|
? ($contains($m,"10s") ? {"type":"usd","usd":0.98} : {"type":"usd","usd":0.49})
|
||||||
|
: $contains($m,"v2-master")
|
||||||
|
? ($contains($m,"10s") ? {"type":"usd","usd":2.8} : {"type":"usd","usd":1.4})
|
||||||
|
: $contains($m,"v1-6")
|
||||||
|
? (
|
||||||
|
$contains($m,"pro")
|
||||||
|
? ($contains($m,"10s") ? {"type":"usd","usd":0.98} : {"type":"usd","usd":0.49})
|
||||||
|
: ($contains($m,"10s") ? {"type":"usd","usd":0.56} : {"type":"usd","usd":0.28})
|
||||||
|
)
|
||||||
|
: $contains($m,"v1")
|
||||||
|
? (
|
||||||
|
$contains($m,"pro")
|
||||||
|
? ($contains($m,"10s") ? {"type":"usd","usd":0.98} : {"type":"usd","usd":0.49})
|
||||||
|
: ($contains($m,"10s") ? {"type":"usd","usd":0.28} : {"type":"usd","usd":0.14})
|
||||||
|
)
|
||||||
|
: {"type":"usd","usd":0.14}
|
||||||
|
)
|
||||||
|
""",
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -1583,6 +1723,9 @@ class KlingVideoExtendNode(IO.ComfyNode):
|
|||||||
IO.Hidden.unique_id,
|
IO.Hidden.unique_id,
|
||||||
],
|
],
|
||||||
is_api_node=True,
|
is_api_node=True,
|
||||||
|
price_badge=IO.PriceBadge(
|
||||||
|
expr="""{"type":"usd","usd":0.28}""",
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -1664,6 +1807,29 @@ class KlingDualCharacterVideoEffectNode(IO.ComfyNode):
|
|||||||
IO.Hidden.unique_id,
|
IO.Hidden.unique_id,
|
||||||
],
|
],
|
||||||
is_api_node=True,
|
is_api_node=True,
|
||||||
|
price_badge=IO.PriceBadge(
|
||||||
|
depends_on=IO.PriceBadgeDepends(widgets=["mode", "model_name", "duration"]),
|
||||||
|
expr="""
|
||||||
|
(
|
||||||
|
$mode := widgets.mode;
|
||||||
|
$model := widgets.model_name;
|
||||||
|
$dur := widgets.duration;
|
||||||
|
($contains($model,"v1-6") or $contains($model,"v1-5"))
|
||||||
|
? (
|
||||||
|
$contains($mode,"pro")
|
||||||
|
? ($contains($dur,"10") ? {"type":"usd","usd":0.98} : {"type":"usd","usd":0.49})
|
||||||
|
: ($contains($dur,"10") ? {"type":"usd","usd":0.56} : {"type":"usd","usd":0.28})
|
||||||
|
)
|
||||||
|
: $contains($model,"v1")
|
||||||
|
? (
|
||||||
|
$contains($mode,"pro")
|
||||||
|
? ($contains($dur,"10") ? {"type":"usd","usd":0.98} : {"type":"usd","usd":0.49})
|
||||||
|
: ($contains($dur,"10") ? {"type":"usd","usd":0.28} : {"type":"usd","usd":0.14})
|
||||||
|
)
|
||||||
|
: {"type":"usd","usd":0.14}
|
||||||
|
)
|
||||||
|
""",
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -1728,6 +1894,16 @@ class KlingSingleImageVideoEffectNode(IO.ComfyNode):
|
|||||||
IO.Hidden.unique_id,
|
IO.Hidden.unique_id,
|
||||||
],
|
],
|
||||||
is_api_node=True,
|
is_api_node=True,
|
||||||
|
price_badge=IO.PriceBadge(
|
||||||
|
depends_on=IO.PriceBadgeDepends(widgets=["effect_scene"]),
|
||||||
|
expr="""
|
||||||
|
(
|
||||||
|
($contains(widgets.effect_scene,"dizzydizzy") or $contains(widgets.effect_scene,"bloombloom"))
|
||||||
|
? {"type":"usd","usd":0.49}
|
||||||
|
: {"type":"usd","usd":0.28}
|
||||||
|
)
|
||||||
|
""",
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -1782,6 +1958,9 @@ class KlingLipSyncAudioToVideoNode(IO.ComfyNode):
|
|||||||
IO.Hidden.unique_id,
|
IO.Hidden.unique_id,
|
||||||
],
|
],
|
||||||
is_api_node=True,
|
is_api_node=True,
|
||||||
|
price_badge=IO.PriceBadge(
|
||||||
|
expr="""{"type":"usd","usd":0.1,"format":{"approximate":true}}""",
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -1842,6 +2021,9 @@ class KlingLipSyncTextToVideoNode(IO.ComfyNode):
|
|||||||
IO.Hidden.unique_id,
|
IO.Hidden.unique_id,
|
||||||
],
|
],
|
||||||
is_api_node=True,
|
is_api_node=True,
|
||||||
|
price_badge=IO.PriceBadge(
|
||||||
|
expr="""{"type":"usd","usd":0.1,"format":{"approximate":true}}""",
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -1892,6 +2074,9 @@ class KlingVirtualTryOnNode(IO.ComfyNode):
|
|||||||
IO.Hidden.unique_id,
|
IO.Hidden.unique_id,
|
||||||
],
|
],
|
||||||
is_api_node=True,
|
is_api_node=True,
|
||||||
|
price_badge=IO.PriceBadge(
|
||||||
|
expr="""{"type":"usd","usd":0.7}""",
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -1991,6 +2176,19 @@ class KlingImageGenerationNode(IO.ComfyNode):
|
|||||||
IO.Hidden.unique_id,
|
IO.Hidden.unique_id,
|
||||||
],
|
],
|
||||||
is_api_node=True,
|
is_api_node=True,
|
||||||
|
price_badge=IO.PriceBadge(
|
||||||
|
depends_on=IO.PriceBadgeDepends(widgets=["model_name", "n"], inputs=["image"]),
|
||||||
|
expr="""
|
||||||
|
(
|
||||||
|
$m := widgets.model_name;
|
||||||
|
$base :=
|
||||||
|
$contains($m,"kling-v1-5")
|
||||||
|
? (inputs.image.connected ? 0.028 : 0.014)
|
||||||
|
: ($contains($m,"kling-v1") ? 0.0035 : 0.014);
|
||||||
|
{"type":"usd","usd": $base * widgets.n}
|
||||||
|
)
|
||||||
|
""",
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -2074,6 +2272,10 @@ class TextToVideoWithAudio(IO.ComfyNode):
|
|||||||
IO.Hidden.unique_id,
|
IO.Hidden.unique_id,
|
||||||
],
|
],
|
||||||
is_api_node=True,
|
is_api_node=True,
|
||||||
|
price_badge=IO.PriceBadge(
|
||||||
|
depends_on=IO.PriceBadgeDepends(widgets=["duration", "generate_audio"]),
|
||||||
|
expr="""{"type":"usd","usd": 0.07 * widgets.duration * (widgets.generate_audio ? 2 : 1)}""",
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -2138,6 +2340,10 @@ class ImageToVideoWithAudio(IO.ComfyNode):
|
|||||||
IO.Hidden.unique_id,
|
IO.Hidden.unique_id,
|
||||||
],
|
],
|
||||||
is_api_node=True,
|
is_api_node=True,
|
||||||
|
price_badge=IO.PriceBadge(
|
||||||
|
depends_on=IO.PriceBadgeDepends(widgets=["duration", "generate_audio"]),
|
||||||
|
expr="""{"type":"usd","usd": 0.07 * widgets.duration * (widgets.generate_audio ? 2 : 1)}""",
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -2218,6 +2424,15 @@ class MotionControl(IO.ComfyNode):
|
|||||||
IO.Hidden.unique_id,
|
IO.Hidden.unique_id,
|
||||||
],
|
],
|
||||||
is_api_node=True,
|
is_api_node=True,
|
||||||
|
price_badge=IO.PriceBadge(
|
||||||
|
depends_on=IO.PriceBadgeDepends(widgets=["mode"]),
|
||||||
|
expr="""
|
||||||
|
(
|
||||||
|
$prices := {"std": 0.07, "pro": 0.112};
|
||||||
|
{"type":"usd","usd": $lookup($prices, widgets.mode), "format":{"suffix":"/second"}}
|
||||||
|
)
|
||||||
|
""",
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
|||||||
@ -28,6 +28,22 @@ class ExecuteTaskRequest(BaseModel):
|
|||||||
image_uri: str | None = Field(None)
|
image_uri: str | None = Field(None)
|
||||||
|
|
||||||
|
|
||||||
|
PRICE_BADGE = IO.PriceBadge(
|
||||||
|
depends_on=IO.PriceBadgeDepends(widgets=["model", "duration", "resolution"]),
|
||||||
|
expr="""
|
||||||
|
(
|
||||||
|
$prices := {
|
||||||
|
"ltx-2 (pro)": {"1920x1080":0.06,"2560x1440":0.12,"3840x2160":0.24},
|
||||||
|
"ltx-2 (fast)": {"1920x1080":0.04,"2560x1440":0.08,"3840x2160":0.16}
|
||||||
|
};
|
||||||
|
$modelPrices := $lookup($prices, $lowercase(widgets.model));
|
||||||
|
$pps := $lookup($modelPrices, widgets.resolution);
|
||||||
|
{"type":"usd","usd": $pps * widgets.duration}
|
||||||
|
)
|
||||||
|
""",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class TextToVideoNode(IO.ComfyNode):
|
class TextToVideoNode(IO.ComfyNode):
|
||||||
@classmethod
|
@classmethod
|
||||||
def define_schema(cls):
|
def define_schema(cls):
|
||||||
@ -69,6 +85,7 @@ class TextToVideoNode(IO.ComfyNode):
|
|||||||
IO.Hidden.unique_id,
|
IO.Hidden.unique_id,
|
||||||
],
|
],
|
||||||
is_api_node=True,
|
is_api_node=True,
|
||||||
|
price_badge=PRICE_BADGE,
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -145,6 +162,7 @@ class ImageToVideoNode(IO.ComfyNode):
|
|||||||
IO.Hidden.unique_id,
|
IO.Hidden.unique_id,
|
||||||
],
|
],
|
||||||
is_api_node=True,
|
is_api_node=True,
|
||||||
|
price_badge=PRICE_BADGE,
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
|||||||
@ -189,6 +189,19 @@ class LumaImageGenerationNode(IO.ComfyNode):
|
|||||||
IO.Hidden.unique_id,
|
IO.Hidden.unique_id,
|
||||||
],
|
],
|
||||||
is_api_node=True,
|
is_api_node=True,
|
||||||
|
price_badge=IO.PriceBadge(
|
||||||
|
depends_on=IO.PriceBadgeDepends(widgets=["model"]),
|
||||||
|
expr="""
|
||||||
|
(
|
||||||
|
$m := widgets.model;
|
||||||
|
$contains($m,"photon-flash-1")
|
||||||
|
? {"type":"usd","usd":0.0027}
|
||||||
|
: $contains($m,"photon-1")
|
||||||
|
? {"type":"usd","usd":0.0104}
|
||||||
|
: {"type":"usd","usd":0.0246}
|
||||||
|
)
|
||||||
|
""",
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -303,6 +316,19 @@ class LumaImageModifyNode(IO.ComfyNode):
|
|||||||
IO.Hidden.unique_id,
|
IO.Hidden.unique_id,
|
||||||
],
|
],
|
||||||
is_api_node=True,
|
is_api_node=True,
|
||||||
|
price_badge=IO.PriceBadge(
|
||||||
|
depends_on=IO.PriceBadgeDepends(widgets=["model"]),
|
||||||
|
expr="""
|
||||||
|
(
|
||||||
|
$m := widgets.model;
|
||||||
|
$contains($m,"photon-flash-1")
|
||||||
|
? {"type":"usd","usd":0.0027}
|
||||||
|
: $contains($m,"photon-1")
|
||||||
|
? {"type":"usd","usd":0.0104}
|
||||||
|
: {"type":"usd","usd":0.0246}
|
||||||
|
)
|
||||||
|
""",
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -395,6 +421,7 @@ class LumaTextToVideoGenerationNode(IO.ComfyNode):
|
|||||||
IO.Hidden.unique_id,
|
IO.Hidden.unique_id,
|
||||||
],
|
],
|
||||||
is_api_node=True,
|
is_api_node=True,
|
||||||
|
price_badge=PRICE_BADGE_VIDEO,
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -505,6 +532,8 @@ class LumaImageToVideoGenerationNode(IO.ComfyNode):
|
|||||||
IO.Hidden.unique_id,
|
IO.Hidden.unique_id,
|
||||||
],
|
],
|
||||||
is_api_node=True,
|
is_api_node=True,
|
||||||
|
price_badge=PRICE_BADGE_VIDEO,
|
||||||
|
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -568,6 +597,53 @@ class LumaImageToVideoGenerationNode(IO.ComfyNode):
|
|||||||
return LumaKeyframes(frame0=frame0, frame1=frame1)
|
return LumaKeyframes(frame0=frame0, frame1=frame1)
|
||||||
|
|
||||||
|
|
||||||
|
PRICE_BADGE_VIDEO = IO.PriceBadge(
|
||||||
|
depends_on=IO.PriceBadgeDepends(widgets=["model", "resolution", "duration"]),
|
||||||
|
expr="""
|
||||||
|
(
|
||||||
|
$p := {
|
||||||
|
"ray-flash-2": {
|
||||||
|
"5s": {"4k":3.13,"1080p":0.79,"720p":0.34,"540p":0.2},
|
||||||
|
"9s": {"4k":5.65,"1080p":1.42,"720p":0.61,"540p":0.36}
|
||||||
|
},
|
||||||
|
"ray-2": {
|
||||||
|
"5s": {"4k":9.11,"1080p":2.27,"720p":1.02,"540p":0.57},
|
||||||
|
"9s": {"4k":16.4,"1080p":4.1,"720p":1.83,"540p":1.03}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
$m := widgets.model;
|
||||||
|
$d := widgets.duration;
|
||||||
|
$r := widgets.resolution;
|
||||||
|
|
||||||
|
$modelKey :=
|
||||||
|
$contains($m,"ray-flash-2") ? "ray-flash-2" :
|
||||||
|
$contains($m,"ray-2") ? "ray-2" :
|
||||||
|
$contains($m,"ray-1-6") ? "ray-1-6" :
|
||||||
|
"other";
|
||||||
|
|
||||||
|
$durKey := $contains($d,"5s") ? "5s" : $contains($d,"9s") ? "9s" : "";
|
||||||
|
$resKey :=
|
||||||
|
$contains($r,"4k") ? "4k" :
|
||||||
|
$contains($r,"1080p") ? "1080p" :
|
||||||
|
$contains($r,"720p") ? "720p" :
|
||||||
|
$contains($r,"540p") ? "540p" : "";
|
||||||
|
|
||||||
|
$modelPrices := $lookup($p, $modelKey);
|
||||||
|
$durPrices := $lookup($modelPrices, $durKey);
|
||||||
|
$v := $lookup($durPrices, $resKey);
|
||||||
|
|
||||||
|
$price :=
|
||||||
|
($modelKey = "ray-1-6") ? 0.5 :
|
||||||
|
($modelKey = "other") ? 0.79 :
|
||||||
|
($exists($v) ? $v : 0.79);
|
||||||
|
|
||||||
|
{"type":"usd","usd": $price}
|
||||||
|
)
|
||||||
|
""",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class LumaExtension(ComfyExtension):
|
class LumaExtension(ComfyExtension):
|
||||||
@override
|
@override
|
||||||
async def get_node_list(self) -> list[type[IO.ComfyNode]]:
|
async def get_node_list(self) -> list[type[IO.ComfyNode]]:
|
||||||
|
|||||||
@ -134,6 +134,9 @@ class MinimaxTextToVideoNode(IO.ComfyNode):
|
|||||||
IO.Hidden.unique_id,
|
IO.Hidden.unique_id,
|
||||||
],
|
],
|
||||||
is_api_node=True,
|
is_api_node=True,
|
||||||
|
price_badge=IO.PriceBadge(
|
||||||
|
expr="""{"type":"usd","usd":0.43}""",
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -197,6 +200,9 @@ class MinimaxImageToVideoNode(IO.ComfyNode):
|
|||||||
IO.Hidden.unique_id,
|
IO.Hidden.unique_id,
|
||||||
],
|
],
|
||||||
is_api_node=True,
|
is_api_node=True,
|
||||||
|
price_badge=IO.PriceBadge(
|
||||||
|
expr="""{"type":"usd","usd":0.43}""",
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -340,6 +346,20 @@ class MinimaxHailuoVideoNode(IO.ComfyNode):
|
|||||||
IO.Hidden.unique_id,
|
IO.Hidden.unique_id,
|
||||||
],
|
],
|
||||||
is_api_node=True,
|
is_api_node=True,
|
||||||
|
price_badge=IO.PriceBadge(
|
||||||
|
depends_on=IO.PriceBadgeDepends(widgets=["resolution", "duration"]),
|
||||||
|
expr="""
|
||||||
|
(
|
||||||
|
$prices := {
|
||||||
|
"768p": {"6": 0.28, "10": 0.56},
|
||||||
|
"1080p": {"6": 0.49}
|
||||||
|
};
|
||||||
|
$resPrices := $lookup($prices, $lowercase(widgets.resolution));
|
||||||
|
$price := $lookup($resPrices, $string(widgets.duration));
|
||||||
|
{"type":"usd","usd": $price ? $price : 0.43}
|
||||||
|
)
|
||||||
|
""",
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
|||||||
@ -233,6 +233,10 @@ class MoonvalleyImg2VideoNode(IO.ComfyNode):
|
|||||||
IO.Hidden.unique_id,
|
IO.Hidden.unique_id,
|
||||||
],
|
],
|
||||||
is_api_node=True,
|
is_api_node=True,
|
||||||
|
price_badge=IO.PriceBadge(
|
||||||
|
depends_on=IO.PriceBadgeDepends(),
|
||||||
|
expr="""{"type":"usd","usd": 1.5}""",
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -351,6 +355,10 @@ class MoonvalleyVideo2VideoNode(IO.ComfyNode):
|
|||||||
IO.Hidden.unique_id,
|
IO.Hidden.unique_id,
|
||||||
],
|
],
|
||||||
is_api_node=True,
|
is_api_node=True,
|
||||||
|
price_badge=IO.PriceBadge(
|
||||||
|
depends_on=IO.PriceBadgeDepends(),
|
||||||
|
expr="""{"type":"usd","usd": 2.25}""",
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -471,6 +479,10 @@ class MoonvalleyTxt2VideoNode(IO.ComfyNode):
|
|||||||
IO.Hidden.unique_id,
|
IO.Hidden.unique_id,
|
||||||
],
|
],
|
||||||
is_api_node=True,
|
is_api_node=True,
|
||||||
|
price_badge=IO.PriceBadge(
|
||||||
|
depends_on=IO.PriceBadgeDepends(),
|
||||||
|
expr="""{"type":"usd","usd": 1.5}""",
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
|||||||
@ -160,6 +160,23 @@ class OpenAIDalle2(IO.ComfyNode):
|
|||||||
IO.Hidden.unique_id,
|
IO.Hidden.unique_id,
|
||||||
],
|
],
|
||||||
is_api_node=True,
|
is_api_node=True,
|
||||||
|
price_badge=IO.PriceBadge(
|
||||||
|
depends_on=IO.PriceBadgeDepends(widgets=["size", "n"]),
|
||||||
|
expr="""
|
||||||
|
(
|
||||||
|
$size := widgets.size;
|
||||||
|
$nRaw := widgets.n;
|
||||||
|
$n := ($nRaw != null and $nRaw != 0) ? $nRaw : 1;
|
||||||
|
|
||||||
|
$base :=
|
||||||
|
$contains($size, "256x256") ? 0.016 :
|
||||||
|
$contains($size, "512x512") ? 0.018 :
|
||||||
|
0.02;
|
||||||
|
|
||||||
|
{"type":"usd","usd": $round($base * $n, 3)}
|
||||||
|
)
|
||||||
|
""",
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -287,6 +304,25 @@ class OpenAIDalle3(IO.ComfyNode):
|
|||||||
IO.Hidden.unique_id,
|
IO.Hidden.unique_id,
|
||||||
],
|
],
|
||||||
is_api_node=True,
|
is_api_node=True,
|
||||||
|
price_badge=IO.PriceBadge(
|
||||||
|
depends_on=IO.PriceBadgeDepends(widgets=["size", "quality"]),
|
||||||
|
expr="""
|
||||||
|
(
|
||||||
|
$size := widgets.size;
|
||||||
|
$q := widgets.quality;
|
||||||
|
$hd := $contains($q, "hd");
|
||||||
|
|
||||||
|
$price :=
|
||||||
|
$contains($size, "1024x1024")
|
||||||
|
? ($hd ? 0.08 : 0.04)
|
||||||
|
: (($contains($size, "1792x1024") or $contains($size, "1024x1792"))
|
||||||
|
? ($hd ? 0.12 : 0.08)
|
||||||
|
: 0.04);
|
||||||
|
|
||||||
|
{"type":"usd","usd": $price}
|
||||||
|
)
|
||||||
|
""",
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -411,6 +447,28 @@ class OpenAIGPTImage1(IO.ComfyNode):
|
|||||||
IO.Hidden.unique_id,
|
IO.Hidden.unique_id,
|
||||||
],
|
],
|
||||||
is_api_node=True,
|
is_api_node=True,
|
||||||
|
price_badge=IO.PriceBadge(
|
||||||
|
depends_on=IO.PriceBadgeDepends(widgets=["quality", "n"]),
|
||||||
|
expr="""
|
||||||
|
(
|
||||||
|
$ranges := {
|
||||||
|
"low": [0.011, 0.02],
|
||||||
|
"medium": [0.046, 0.07],
|
||||||
|
"high": [0.167, 0.3]
|
||||||
|
};
|
||||||
|
$range := $lookup($ranges, widgets.quality);
|
||||||
|
$n := widgets.n;
|
||||||
|
($n = 1)
|
||||||
|
? {"type":"range_usd","min_usd": $range[0], "max_usd": $range[1]}
|
||||||
|
: {
|
||||||
|
"type":"range_usd",
|
||||||
|
"min_usd": $range[0],
|
||||||
|
"max_usd": $range[1],
|
||||||
|
"format": { "suffix": " x " & $string($n) & "/Run" }
|
||||||
|
}
|
||||||
|
)
|
||||||
|
""",
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -566,6 +624,75 @@ class OpenAIChatNode(IO.ComfyNode):
|
|||||||
IO.Hidden.unique_id,
|
IO.Hidden.unique_id,
|
||||||
],
|
],
|
||||||
is_api_node=True,
|
is_api_node=True,
|
||||||
|
price_badge=IO.PriceBadge(
|
||||||
|
depends_on=IO.PriceBadgeDepends(widgets=["model"]),
|
||||||
|
expr="""
|
||||||
|
(
|
||||||
|
$m := widgets.model;
|
||||||
|
$contains($m, "o4-mini") ? {
|
||||||
|
"type": "list_usd",
|
||||||
|
"usd": [0.0011, 0.0044],
|
||||||
|
"format": { "approximate": true, "separator": "-", "suffix": " per 1K tokens" }
|
||||||
|
}
|
||||||
|
: $contains($m, "o1-pro") ? {
|
||||||
|
"type": "list_usd",
|
||||||
|
"usd": [0.15, 0.6],
|
||||||
|
"format": { "approximate": true, "separator": "-", "suffix": " per 1K tokens" }
|
||||||
|
}
|
||||||
|
: $contains($m, "o1") ? {
|
||||||
|
"type": "list_usd",
|
||||||
|
"usd": [0.015, 0.06],
|
||||||
|
"format": { "approximate": true, "separator": "-", "suffix": " per 1K tokens" }
|
||||||
|
}
|
||||||
|
: $contains($m, "o3-mini") ? {
|
||||||
|
"type": "list_usd",
|
||||||
|
"usd": [0.0011, 0.0044],
|
||||||
|
"format": { "approximate": true, "separator": "-", "suffix": " per 1K tokens" }
|
||||||
|
}
|
||||||
|
: $contains($m, "o3") ? {
|
||||||
|
"type": "list_usd",
|
||||||
|
"usd": [0.01, 0.04],
|
||||||
|
"format": { "approximate": true, "separator": "-", "suffix": " per 1K tokens" }
|
||||||
|
}
|
||||||
|
: $contains($m, "gpt-4o") ? {
|
||||||
|
"type": "list_usd",
|
||||||
|
"usd": [0.0025, 0.01],
|
||||||
|
"format": { "approximate": true, "separator": "-", "suffix": " per 1K tokens" }
|
||||||
|
}
|
||||||
|
: $contains($m, "gpt-4.1-nano") ? {
|
||||||
|
"type": "list_usd",
|
||||||
|
"usd": [0.0001, 0.0004],
|
||||||
|
"format": { "approximate": true, "separator": "-", "suffix": " per 1K tokens" }
|
||||||
|
}
|
||||||
|
: $contains($m, "gpt-4.1-mini") ? {
|
||||||
|
"type": "list_usd",
|
||||||
|
"usd": [0.0004, 0.0016],
|
||||||
|
"format": { "approximate": true, "separator": "-", "suffix": " per 1K tokens" }
|
||||||
|
}
|
||||||
|
: $contains($m, "gpt-4.1") ? {
|
||||||
|
"type": "list_usd",
|
||||||
|
"usd": [0.002, 0.008],
|
||||||
|
"format": { "approximate": true, "separator": "-", "suffix": " per 1K tokens" }
|
||||||
|
}
|
||||||
|
: $contains($m, "gpt-5-nano") ? {
|
||||||
|
"type": "list_usd",
|
||||||
|
"usd": [0.00005, 0.0004],
|
||||||
|
"format": { "approximate": true, "separator": "-", "suffix": " per 1K tokens" }
|
||||||
|
}
|
||||||
|
: $contains($m, "gpt-5-mini") ? {
|
||||||
|
"type": "list_usd",
|
||||||
|
"usd": [0.00025, 0.002],
|
||||||
|
"format": { "approximate": true, "separator": "-", "suffix": " per 1K tokens" }
|
||||||
|
}
|
||||||
|
: $contains($m, "gpt-5") ? {
|
||||||
|
"type": "list_usd",
|
||||||
|
"usd": [0.00125, 0.01],
|
||||||
|
"format": { "approximate": true, "separator": "-", "suffix": " per 1K tokens" }
|
||||||
|
}
|
||||||
|
: {"type": "text", "text": "Token-based"}
|
||||||
|
)
|
||||||
|
""",
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
|||||||
@ -128,6 +128,7 @@ class PixverseTextToVideoNode(IO.ComfyNode):
|
|||||||
IO.Hidden.unique_id,
|
IO.Hidden.unique_id,
|
||||||
],
|
],
|
||||||
is_api_node=True,
|
is_api_node=True,
|
||||||
|
price_badge=PRICE_BADGE_VIDEO,
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -242,6 +243,7 @@ class PixverseImageToVideoNode(IO.ComfyNode):
|
|||||||
IO.Hidden.unique_id,
|
IO.Hidden.unique_id,
|
||||||
],
|
],
|
||||||
is_api_node=True,
|
is_api_node=True,
|
||||||
|
price_badge=PRICE_BADGE_VIDEO,
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -355,6 +357,7 @@ class PixverseTransitionVideoNode(IO.ComfyNode):
|
|||||||
IO.Hidden.unique_id,
|
IO.Hidden.unique_id,
|
||||||
],
|
],
|
||||||
is_api_node=True,
|
is_api_node=True,
|
||||||
|
price_badge=PRICE_BADGE_VIDEO,
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -416,6 +419,33 @@ class PixverseTransitionVideoNode(IO.ComfyNode):
|
|||||||
return IO.NodeOutput(await download_url_to_video_output(response_poll.Resp.url))
|
return IO.NodeOutput(await download_url_to_video_output(response_poll.Resp.url))
|
||||||
|
|
||||||
|
|
||||||
|
PRICE_BADGE_VIDEO = IO.PriceBadge(
|
||||||
|
depends_on=IO.PriceBadgeDepends(widgets=["duration_seconds", "quality", "motion_mode"]),
|
||||||
|
expr="""
|
||||||
|
(
|
||||||
|
$prices := {
|
||||||
|
"5": {
|
||||||
|
"1080p": {"normal": 1.2, "fast": 1.2},
|
||||||
|
"720p": {"normal": 0.6, "fast": 1.2},
|
||||||
|
"540p": {"normal": 0.45, "fast": 0.9},
|
||||||
|
"360p": {"normal": 0.45, "fast": 0.9}
|
||||||
|
},
|
||||||
|
"8": {
|
||||||
|
"1080p": {"normal": 1.2, "fast": 1.2},
|
||||||
|
"720p": {"normal": 1.2, "fast": 1.2},
|
||||||
|
"540p": {"normal": 0.9, "fast": 1.2},
|
||||||
|
"360p": {"normal": 0.9, "fast": 1.2}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
$durPrices := $lookup($prices, $string(widgets.duration_seconds));
|
||||||
|
$qualityPrices := $lookup($durPrices, widgets.quality);
|
||||||
|
$price := $lookup($qualityPrices, widgets.motion_mode);
|
||||||
|
{"type":"usd","usd": $price ? $price : 0.9}
|
||||||
|
)
|
||||||
|
""",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class PixVerseExtension(ComfyExtension):
|
class PixVerseExtension(ComfyExtension):
|
||||||
@override
|
@override
|
||||||
async def get_node_list(self) -> list[type[IO.ComfyNode]]:
|
async def get_node_list(self) -> list[type[IO.ComfyNode]]:
|
||||||
|
|||||||
@ -378,6 +378,10 @@ class RecraftTextToImageNode(IO.ComfyNode):
|
|||||||
IO.Hidden.unique_id,
|
IO.Hidden.unique_id,
|
||||||
],
|
],
|
||||||
is_api_node=True,
|
is_api_node=True,
|
||||||
|
price_badge=IO.PriceBadge(
|
||||||
|
depends_on=IO.PriceBadgeDepends(widgets=["n"]),
|
||||||
|
expr="""{"type":"usd","usd": $round(0.04 * widgets.n, 2)}""",
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -490,6 +494,10 @@ class RecraftImageToImageNode(IO.ComfyNode):
|
|||||||
IO.Hidden.unique_id,
|
IO.Hidden.unique_id,
|
||||||
],
|
],
|
||||||
is_api_node=True,
|
is_api_node=True,
|
||||||
|
price_badge=IO.PriceBadge(
|
||||||
|
depends_on=IO.PriceBadgeDepends(widgets=["n"]),
|
||||||
|
expr="""{"type":"usd","usd": $round(0.04 * widgets.n, 2)}""",
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -591,6 +599,10 @@ class RecraftImageInpaintingNode(IO.ComfyNode):
|
|||||||
IO.Hidden.unique_id,
|
IO.Hidden.unique_id,
|
||||||
],
|
],
|
||||||
is_api_node=True,
|
is_api_node=True,
|
||||||
|
price_badge=IO.PriceBadge(
|
||||||
|
depends_on=IO.PriceBadgeDepends(widgets=["n"]),
|
||||||
|
expr="""{"type":"usd","usd": $round(0.04 * widgets.n, 2)}""",
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -692,6 +704,10 @@ class RecraftTextToVectorNode(IO.ComfyNode):
|
|||||||
IO.Hidden.unique_id,
|
IO.Hidden.unique_id,
|
||||||
],
|
],
|
||||||
is_api_node=True,
|
is_api_node=True,
|
||||||
|
price_badge=IO.PriceBadge(
|
||||||
|
depends_on=IO.PriceBadgeDepends(widgets=["n"]),
|
||||||
|
expr="""{"type":"usd","usd": $round(0.08 * widgets.n, 2)}""",
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -759,6 +775,10 @@ class RecraftVectorizeImageNode(IO.ComfyNode):
|
|||||||
IO.Hidden.unique_id,
|
IO.Hidden.unique_id,
|
||||||
],
|
],
|
||||||
is_api_node=True,
|
is_api_node=True,
|
||||||
|
price_badge=IO.PriceBadge(
|
||||||
|
depends_on=IO.PriceBadgeDepends(),
|
||||||
|
expr="""{"type":"usd","usd": 0.01}""",
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -817,6 +837,9 @@ class RecraftReplaceBackgroundNode(IO.ComfyNode):
|
|||||||
IO.Hidden.unique_id,
|
IO.Hidden.unique_id,
|
||||||
],
|
],
|
||||||
is_api_node=True,
|
is_api_node=True,
|
||||||
|
price_badge=IO.PriceBadge(
|
||||||
|
expr="""{"type":"usd","usd":0.04}""",
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -883,6 +906,9 @@ class RecraftRemoveBackgroundNode(IO.ComfyNode):
|
|||||||
IO.Hidden.unique_id,
|
IO.Hidden.unique_id,
|
||||||
],
|
],
|
||||||
is_api_node=True,
|
is_api_node=True,
|
||||||
|
price_badge=IO.PriceBadge(
|
||||||
|
expr="""{"type":"usd","usd":0.01}""",
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -929,6 +955,9 @@ class RecraftCrispUpscaleNode(IO.ComfyNode):
|
|||||||
IO.Hidden.unique_id,
|
IO.Hidden.unique_id,
|
||||||
],
|
],
|
||||||
is_api_node=True,
|
is_api_node=True,
|
||||||
|
price_badge=IO.PriceBadge(
|
||||||
|
expr="""{"type":"usd","usd":0.004}""",
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -972,6 +1001,9 @@ class RecraftCreativeUpscaleNode(RecraftCrispUpscaleNode):
|
|||||||
IO.Hidden.unique_id,
|
IO.Hidden.unique_id,
|
||||||
],
|
],
|
||||||
is_api_node=True,
|
is_api_node=True,
|
||||||
|
price_badge=IO.PriceBadge(
|
||||||
|
expr="""{"type":"usd","usd":0.25}""",
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -241,6 +241,9 @@ class Rodin3D_Regular(IO.ComfyNode):
|
|||||||
IO.Hidden.unique_id,
|
IO.Hidden.unique_id,
|
||||||
],
|
],
|
||||||
is_api_node=True,
|
is_api_node=True,
|
||||||
|
price_badge=IO.PriceBadge(
|
||||||
|
expr="""{"type":"usd","usd":0.4}""",
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -294,6 +297,9 @@ class Rodin3D_Detail(IO.ComfyNode):
|
|||||||
IO.Hidden.unique_id,
|
IO.Hidden.unique_id,
|
||||||
],
|
],
|
||||||
is_api_node=True,
|
is_api_node=True,
|
||||||
|
price_badge=IO.PriceBadge(
|
||||||
|
expr="""{"type":"usd","usd":0.4}""",
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -347,6 +353,9 @@ class Rodin3D_Smooth(IO.ComfyNode):
|
|||||||
IO.Hidden.unique_id,
|
IO.Hidden.unique_id,
|
||||||
],
|
],
|
||||||
is_api_node=True,
|
is_api_node=True,
|
||||||
|
price_badge=IO.PriceBadge(
|
||||||
|
expr="""{"type":"usd","usd":0.4}""",
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -406,6 +415,9 @@ class Rodin3D_Sketch(IO.ComfyNode):
|
|||||||
IO.Hidden.unique_id,
|
IO.Hidden.unique_id,
|
||||||
],
|
],
|
||||||
is_api_node=True,
|
is_api_node=True,
|
||||||
|
price_badge=IO.PriceBadge(
|
||||||
|
expr="""{"type":"usd","usd":0.4}""",
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
|||||||
@ -184,6 +184,10 @@ class RunwayImageToVideoNodeGen3a(IO.ComfyNode):
|
|||||||
IO.Hidden.unique_id,
|
IO.Hidden.unique_id,
|
||||||
],
|
],
|
||||||
is_api_node=True,
|
is_api_node=True,
|
||||||
|
price_badge=IO.PriceBadge(
|
||||||
|
depends_on=IO.PriceBadgeDepends(widgets=["duration"]),
|
||||||
|
expr="""{"type":"usd","usd": 0.0715 * widgets.duration}""",
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -274,6 +278,10 @@ class RunwayImageToVideoNodeGen4(IO.ComfyNode):
|
|||||||
IO.Hidden.unique_id,
|
IO.Hidden.unique_id,
|
||||||
],
|
],
|
||||||
is_api_node=True,
|
is_api_node=True,
|
||||||
|
price_badge=IO.PriceBadge(
|
||||||
|
depends_on=IO.PriceBadgeDepends(widgets=["duration"]),
|
||||||
|
expr="""{"type":"usd","usd": 0.0715 * widgets.duration}""",
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -372,6 +380,10 @@ class RunwayFirstLastFrameNode(IO.ComfyNode):
|
|||||||
IO.Hidden.unique_id,
|
IO.Hidden.unique_id,
|
||||||
],
|
],
|
||||||
is_api_node=True,
|
is_api_node=True,
|
||||||
|
price_badge=IO.PriceBadge(
|
||||||
|
depends_on=IO.PriceBadgeDepends(widgets=["duration"]),
|
||||||
|
expr="""{"type":"usd","usd": 0.0715 * widgets.duration}""",
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -457,6 +469,9 @@ class RunwayTextToImageNode(IO.ComfyNode):
|
|||||||
IO.Hidden.unique_id,
|
IO.Hidden.unique_id,
|
||||||
],
|
],
|
||||||
is_api_node=True,
|
is_api_node=True,
|
||||||
|
price_badge=IO.PriceBadge(
|
||||||
|
expr="""{"type":"usd","usd":0.11}""",
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
|||||||
@ -89,6 +89,24 @@ class OpenAIVideoSora2(IO.ComfyNode):
|
|||||||
IO.Hidden.unique_id,
|
IO.Hidden.unique_id,
|
||||||
],
|
],
|
||||||
is_api_node=True,
|
is_api_node=True,
|
||||||
|
price_badge=IO.PriceBadge(
|
||||||
|
depends_on=IO.PriceBadgeDepends(widgets=["model", "size", "duration"]),
|
||||||
|
expr="""
|
||||||
|
(
|
||||||
|
$m := widgets.model;
|
||||||
|
$size := widgets.size;
|
||||||
|
$dur := widgets.duration;
|
||||||
|
$isPro := $contains($m, "sora-2-pro");
|
||||||
|
$isSora2 := $contains($m, "sora-2");
|
||||||
|
$isProSize := ($size = "1024x1792" or $size = "1792x1024");
|
||||||
|
$perSec :=
|
||||||
|
$isPro ? ($isProSize ? 0.5 : 0.3) :
|
||||||
|
$isSora2 ? 0.1 :
|
||||||
|
($isProSize ? 0.5 : 0.1);
|
||||||
|
{"type":"usd","usd": $round($perSec * $dur, 2)}
|
||||||
|
)
|
||||||
|
""",
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
|||||||
@ -127,6 +127,9 @@ class StabilityStableImageUltraNode(IO.ComfyNode):
|
|||||||
IO.Hidden.unique_id,
|
IO.Hidden.unique_id,
|
||||||
],
|
],
|
||||||
is_api_node=True,
|
is_api_node=True,
|
||||||
|
price_badge=IO.PriceBadge(
|
||||||
|
expr="""{"type":"usd","usd":0.08}""",
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -264,6 +267,16 @@ class StabilityStableImageSD_3_5Node(IO.ComfyNode):
|
|||||||
IO.Hidden.unique_id,
|
IO.Hidden.unique_id,
|
||||||
],
|
],
|
||||||
is_api_node=True,
|
is_api_node=True,
|
||||||
|
price_badge=IO.PriceBadge(
|
||||||
|
depends_on=IO.PriceBadgeDepends(widgets=["model"]),
|
||||||
|
expr="""
|
||||||
|
(
|
||||||
|
$contains(widgets.model,"large")
|
||||||
|
? {"type":"usd","usd":0.065}
|
||||||
|
: {"type":"usd","usd":0.035}
|
||||||
|
)
|
||||||
|
""",
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -382,6 +395,9 @@ class StabilityUpscaleConservativeNode(IO.ComfyNode):
|
|||||||
IO.Hidden.unique_id,
|
IO.Hidden.unique_id,
|
||||||
],
|
],
|
||||||
is_api_node=True,
|
is_api_node=True,
|
||||||
|
price_badge=IO.PriceBadge(
|
||||||
|
expr="""{"type":"usd","usd":0.25}""",
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -486,6 +502,9 @@ class StabilityUpscaleCreativeNode(IO.ComfyNode):
|
|||||||
IO.Hidden.unique_id,
|
IO.Hidden.unique_id,
|
||||||
],
|
],
|
||||||
is_api_node=True,
|
is_api_node=True,
|
||||||
|
price_badge=IO.PriceBadge(
|
||||||
|
expr="""{"type":"usd","usd":0.25}""",
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -566,6 +585,9 @@ class StabilityUpscaleFastNode(IO.ComfyNode):
|
|||||||
IO.Hidden.unique_id,
|
IO.Hidden.unique_id,
|
||||||
],
|
],
|
||||||
is_api_node=True,
|
is_api_node=True,
|
||||||
|
price_badge=IO.PriceBadge(
|
||||||
|
expr="""{"type":"usd","usd":0.01}""",
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -648,6 +670,9 @@ class StabilityTextToAudio(IO.ComfyNode):
|
|||||||
IO.Hidden.unique_id,
|
IO.Hidden.unique_id,
|
||||||
],
|
],
|
||||||
is_api_node=True,
|
is_api_node=True,
|
||||||
|
price_badge=IO.PriceBadge(
|
||||||
|
expr="""{"type":"usd","usd":0.2}""",
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -732,6 +757,9 @@ class StabilityAudioToAudio(IO.ComfyNode):
|
|||||||
IO.Hidden.unique_id,
|
IO.Hidden.unique_id,
|
||||||
],
|
],
|
||||||
is_api_node=True,
|
is_api_node=True,
|
||||||
|
price_badge=IO.PriceBadge(
|
||||||
|
expr="""{"type":"usd","usd":0.2}""",
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -828,6 +856,9 @@ class StabilityAudioInpaint(IO.ComfyNode):
|
|||||||
IO.Hidden.unique_id,
|
IO.Hidden.unique_id,
|
||||||
],
|
],
|
||||||
is_api_node=True,
|
is_api_node=True,
|
||||||
|
price_badge=IO.PriceBadge(
|
||||||
|
expr="""{"type":"usd","usd":0.2}""",
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
|||||||
@ -117,6 +117,38 @@ class TripoTextToModelNode(IO.ComfyNode):
|
|||||||
],
|
],
|
||||||
is_api_node=True,
|
is_api_node=True,
|
||||||
is_output_node=True,
|
is_output_node=True,
|
||||||
|
price_badge=IO.PriceBadge(
|
||||||
|
depends_on=IO.PriceBadgeDepends(
|
||||||
|
widgets=[
|
||||||
|
"model_version",
|
||||||
|
"style",
|
||||||
|
"texture",
|
||||||
|
"pbr",
|
||||||
|
"quad",
|
||||||
|
"texture_quality",
|
||||||
|
"geometry_quality",
|
||||||
|
],
|
||||||
|
),
|
||||||
|
expr="""
|
||||||
|
(
|
||||||
|
$isV14 := $contains(widgets.model_version,"v1.4");
|
||||||
|
$style := widgets.style;
|
||||||
|
$hasStyle := ($style != "" and $style != "none");
|
||||||
|
$withTexture := widgets.texture or widgets.pbr;
|
||||||
|
$isHdTexture := (widgets.texture_quality = "detailed");
|
||||||
|
$isDetailedGeometry := (widgets.geometry_quality = "detailed");
|
||||||
|
$baseCredits :=
|
||||||
|
$isV14 ? 20 : ($withTexture ? 20 : 10);
|
||||||
|
$credits :=
|
||||||
|
$baseCredits
|
||||||
|
+ ($hasStyle ? 5 : 0)
|
||||||
|
+ (widgets.quad ? 5 : 0)
|
||||||
|
+ ($isHdTexture ? 10 : 0)
|
||||||
|
+ ($isDetailedGeometry ? 20 : 0);
|
||||||
|
{"type":"usd","usd": $round($credits * 0.01, 2)}
|
||||||
|
)
|
||||||
|
""",
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -210,6 +242,38 @@ class TripoImageToModelNode(IO.ComfyNode):
|
|||||||
],
|
],
|
||||||
is_api_node=True,
|
is_api_node=True,
|
||||||
is_output_node=True,
|
is_output_node=True,
|
||||||
|
price_badge=IO.PriceBadge(
|
||||||
|
depends_on=IO.PriceBadgeDepends(
|
||||||
|
widgets=[
|
||||||
|
"model_version",
|
||||||
|
"style",
|
||||||
|
"texture",
|
||||||
|
"pbr",
|
||||||
|
"quad",
|
||||||
|
"texture_quality",
|
||||||
|
"geometry_quality",
|
||||||
|
],
|
||||||
|
),
|
||||||
|
expr="""
|
||||||
|
(
|
||||||
|
$isV14 := $contains(widgets.model_version,"v1.4");
|
||||||
|
$style := widgets.style;
|
||||||
|
$hasStyle := ($style != "" and $style != "none");
|
||||||
|
$withTexture := widgets.texture or widgets.pbr;
|
||||||
|
$isHdTexture := (widgets.texture_quality = "detailed");
|
||||||
|
$isDetailedGeometry := (widgets.geometry_quality = "detailed");
|
||||||
|
$baseCredits :=
|
||||||
|
$isV14 ? 30 : ($withTexture ? 30 : 20);
|
||||||
|
$credits :=
|
||||||
|
$baseCredits
|
||||||
|
+ ($hasStyle ? 5 : 0)
|
||||||
|
+ (widgets.quad ? 5 : 0)
|
||||||
|
+ ($isHdTexture ? 10 : 0)
|
||||||
|
+ ($isDetailedGeometry ? 20 : 0);
|
||||||
|
{"type":"usd","usd": $round($credits * 0.01, 2)}
|
||||||
|
)
|
||||||
|
""",
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -314,6 +378,34 @@ class TripoMultiviewToModelNode(IO.ComfyNode):
|
|||||||
],
|
],
|
||||||
is_api_node=True,
|
is_api_node=True,
|
||||||
is_output_node=True,
|
is_output_node=True,
|
||||||
|
price_badge=IO.PriceBadge(
|
||||||
|
depends_on=IO.PriceBadgeDepends(
|
||||||
|
widgets=[
|
||||||
|
"model_version",
|
||||||
|
"texture",
|
||||||
|
"pbr",
|
||||||
|
"quad",
|
||||||
|
"texture_quality",
|
||||||
|
"geometry_quality",
|
||||||
|
],
|
||||||
|
),
|
||||||
|
expr="""
|
||||||
|
(
|
||||||
|
$isV14 := $contains(widgets.model_version,"v1.4");
|
||||||
|
$withTexture := widgets.texture or widgets.pbr;
|
||||||
|
$isHdTexture := (widgets.texture_quality = "detailed");
|
||||||
|
$isDetailedGeometry := (widgets.geometry_quality = "detailed");
|
||||||
|
$baseCredits :=
|
||||||
|
$isV14 ? 30 : ($withTexture ? 30 : 20);
|
||||||
|
$credits :=
|
||||||
|
$baseCredits
|
||||||
|
+ (widgets.quad ? 5 : 0)
|
||||||
|
+ ($isHdTexture ? 10 : 0)
|
||||||
|
+ ($isDetailedGeometry ? 20 : 0);
|
||||||
|
{"type":"usd","usd": $round($credits * 0.01, 2)}
|
||||||
|
)
|
||||||
|
""",
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -405,6 +497,15 @@ class TripoTextureNode(IO.ComfyNode):
|
|||||||
],
|
],
|
||||||
is_api_node=True,
|
is_api_node=True,
|
||||||
is_output_node=True,
|
is_output_node=True,
|
||||||
|
price_badge=IO.PriceBadge(
|
||||||
|
depends_on=IO.PriceBadgeDepends(widgets=["texture_quality"]),
|
||||||
|
expr="""
|
||||||
|
(
|
||||||
|
$tq := widgets.texture_quality;
|
||||||
|
{"type":"usd","usd": ($contains($tq,"detailed") ? 0.2 : 0.1)}
|
||||||
|
)
|
||||||
|
""",
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -456,6 +557,9 @@ class TripoRefineNode(IO.ComfyNode):
|
|||||||
],
|
],
|
||||||
is_api_node=True,
|
is_api_node=True,
|
||||||
is_output_node=True,
|
is_output_node=True,
|
||||||
|
price_badge=IO.PriceBadge(
|
||||||
|
expr="""{"type":"usd","usd":0.3}""",
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -489,6 +593,9 @@ class TripoRigNode(IO.ComfyNode):
|
|||||||
],
|
],
|
||||||
is_api_node=True,
|
is_api_node=True,
|
||||||
is_output_node=True,
|
is_output_node=True,
|
||||||
|
price_badge=IO.PriceBadge(
|
||||||
|
expr="""{"type":"usd","usd":0.25}""",
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -545,6 +652,9 @@ class TripoRetargetNode(IO.ComfyNode):
|
|||||||
],
|
],
|
||||||
is_api_node=True,
|
is_api_node=True,
|
||||||
is_output_node=True,
|
is_output_node=True,
|
||||||
|
price_badge=IO.PriceBadge(
|
||||||
|
expr="""{"type":"usd","usd":0.1}""",
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -638,6 +748,60 @@ class TripoConversionNode(IO.ComfyNode):
|
|||||||
],
|
],
|
||||||
is_api_node=True,
|
is_api_node=True,
|
||||||
is_output_node=True,
|
is_output_node=True,
|
||||||
|
price_badge=IO.PriceBadge(
|
||||||
|
depends_on=IO.PriceBadgeDepends(
|
||||||
|
widgets=[
|
||||||
|
"quad",
|
||||||
|
"face_limit",
|
||||||
|
"texture_size",
|
||||||
|
"texture_format",
|
||||||
|
"force_symmetry",
|
||||||
|
"flatten_bottom",
|
||||||
|
"flatten_bottom_threshold",
|
||||||
|
"pivot_to_center_bottom",
|
||||||
|
"scale_factor",
|
||||||
|
"with_animation",
|
||||||
|
"pack_uv",
|
||||||
|
"bake",
|
||||||
|
"part_names",
|
||||||
|
"fbx_preset",
|
||||||
|
"export_vertex_colors",
|
||||||
|
"export_orientation",
|
||||||
|
"animate_in_place",
|
||||||
|
],
|
||||||
|
),
|
||||||
|
expr="""
|
||||||
|
(
|
||||||
|
$face := (widgets.face_limit != null) ? widgets.face_limit : -1;
|
||||||
|
$texSize := (widgets.texture_size != null) ? widgets.texture_size : 4096;
|
||||||
|
$flatThresh := (widgets.flatten_bottom_threshold != null) ? widgets.flatten_bottom_threshold : 0;
|
||||||
|
$scale := (widgets.scale_factor != null) ? widgets.scale_factor : 1;
|
||||||
|
$texFmt := (widgets.texture_format != "" ? widgets.texture_format : "jpeg");
|
||||||
|
$part := widgets.part_names;
|
||||||
|
$fbx := (widgets.fbx_preset != "" ? widgets.fbx_preset : "blender");
|
||||||
|
$orient := (widgets.export_orientation != "" ? widgets.export_orientation : "default");
|
||||||
|
$advanced :=
|
||||||
|
widgets.quad or
|
||||||
|
widgets.force_symmetry or
|
||||||
|
widgets.flatten_bottom or
|
||||||
|
widgets.pivot_to_center_bottom or
|
||||||
|
widgets.with_animation or
|
||||||
|
widgets.pack_uv or
|
||||||
|
widgets.bake or
|
||||||
|
widgets.export_vertex_colors or
|
||||||
|
widgets.animate_in_place or
|
||||||
|
($face != -1) or
|
||||||
|
($texSize != 4096) or
|
||||||
|
($flatThresh != 0) or
|
||||||
|
($scale != 1) or
|
||||||
|
($texFmt != "jpeg") or
|
||||||
|
($part != "") or
|
||||||
|
($fbx != "blender") or
|
||||||
|
($orient != "default");
|
||||||
|
{"type":"usd","usd": ($advanced ? 0.1 : 0.05)}
|
||||||
|
)
|
||||||
|
""",
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
|||||||
@ -122,6 +122,10 @@ class VeoVideoGenerationNode(IO.ComfyNode):
|
|||||||
IO.Hidden.unique_id,
|
IO.Hidden.unique_id,
|
||||||
],
|
],
|
||||||
is_api_node=True,
|
is_api_node=True,
|
||||||
|
price_badge=IO.PriceBadge(
|
||||||
|
depends_on=IO.PriceBadgeDepends(widgets=["duration_seconds"]),
|
||||||
|
expr="""{"type":"usd","usd": 0.5 * widgets.duration_seconds}""",
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -347,6 +351,20 @@ class Veo3VideoGenerationNode(VeoVideoGenerationNode):
|
|||||||
IO.Hidden.unique_id,
|
IO.Hidden.unique_id,
|
||||||
],
|
],
|
||||||
is_api_node=True,
|
is_api_node=True,
|
||||||
|
price_badge=IO.PriceBadge(
|
||||||
|
depends_on=IO.PriceBadgeDepends(widgets=["model", "generate_audio"]),
|
||||||
|
expr="""
|
||||||
|
(
|
||||||
|
$m := widgets.model;
|
||||||
|
$a := widgets.generate_audio;
|
||||||
|
($contains($m,"veo-3.0-fast-generate-001") or $contains($m,"veo-3.1-fast-generate"))
|
||||||
|
? {"type":"usd","usd": ($a ? 1.2 : 0.8)}
|
||||||
|
: ($contains($m,"veo-3.0-generate-001") or $contains($m,"veo-3.1-generate"))
|
||||||
|
? {"type":"usd","usd": ($a ? 3.2 : 1.6)}
|
||||||
|
: {"type":"range_usd","min_usd":0.8,"max_usd":3.2}
|
||||||
|
)
|
||||||
|
""",
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -420,6 +438,30 @@ class Veo3FirstLastFrameNode(IO.ComfyNode):
|
|||||||
IO.Hidden.unique_id,
|
IO.Hidden.unique_id,
|
||||||
],
|
],
|
||||||
is_api_node=True,
|
is_api_node=True,
|
||||||
|
price_badge=IO.PriceBadge(
|
||||||
|
depends_on=IO.PriceBadgeDepends(widgets=["model", "generate_audio", "duration"]),
|
||||||
|
expr="""
|
||||||
|
(
|
||||||
|
$prices := {
|
||||||
|
"veo-3.1-fast-generate": { "audio": 0.15, "no_audio": 0.10 },
|
||||||
|
"veo-3.1-generate": { "audio": 0.40, "no_audio": 0.20 }
|
||||||
|
};
|
||||||
|
$m := widgets.model;
|
||||||
|
$ga := (widgets.generate_audio = "true");
|
||||||
|
$seconds := widgets.duration;
|
||||||
|
$modelKey :=
|
||||||
|
$contains($m, "veo-3.1-fast-generate") ? "veo-3.1-fast-generate" :
|
||||||
|
$contains($m, "veo-3.1-generate") ? "veo-3.1-generate" :
|
||||||
|
"";
|
||||||
|
$audioKey := $ga ? "audio" : "no_audio";
|
||||||
|
$modelPrices := $lookup($prices, $modelKey);
|
||||||
|
$pps := $lookup($modelPrices, $audioKey);
|
||||||
|
($pps != null)
|
||||||
|
? {"type":"usd","usd": $pps * $seconds}
|
||||||
|
: {"type":"range_usd","min_usd": 0.4, "max_usd": 3.2}
|
||||||
|
)
|
||||||
|
""",
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
|||||||
@ -121,6 +121,9 @@ class ViduTextToVideoNode(IO.ComfyNode):
|
|||||||
IO.Hidden.unique_id,
|
IO.Hidden.unique_id,
|
||||||
],
|
],
|
||||||
is_api_node=True,
|
is_api_node=True,
|
||||||
|
price_badge=IO.PriceBadge(
|
||||||
|
expr="""{"type":"usd","usd":0.4}""",
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -214,6 +217,9 @@ class ViduImageToVideoNode(IO.ComfyNode):
|
|||||||
IO.Hidden.unique_id,
|
IO.Hidden.unique_id,
|
||||||
],
|
],
|
||||||
is_api_node=True,
|
is_api_node=True,
|
||||||
|
price_badge=IO.PriceBadge(
|
||||||
|
expr="""{"type":"usd","usd":0.4}""",
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -317,6 +323,9 @@ class ViduReferenceVideoNode(IO.ComfyNode):
|
|||||||
IO.Hidden.unique_id,
|
IO.Hidden.unique_id,
|
||||||
],
|
],
|
||||||
is_api_node=True,
|
is_api_node=True,
|
||||||
|
price_badge=IO.PriceBadge(
|
||||||
|
expr="""{"type":"usd","usd":0.4}""",
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -426,6 +435,9 @@ class ViduStartEndToVideoNode(IO.ComfyNode):
|
|||||||
IO.Hidden.unique_id,
|
IO.Hidden.unique_id,
|
||||||
],
|
],
|
||||||
is_api_node=True,
|
is_api_node=True,
|
||||||
|
price_badge=IO.PriceBadge(
|
||||||
|
expr="""{"type":"usd","usd":0.4}""",
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -507,6 +519,17 @@ class Vidu2TextToVideoNode(IO.ComfyNode):
|
|||||||
IO.Hidden.unique_id,
|
IO.Hidden.unique_id,
|
||||||
],
|
],
|
||||||
is_api_node=True,
|
is_api_node=True,
|
||||||
|
price_badge=IO.PriceBadge(
|
||||||
|
depends_on=IO.PriceBadgeDepends(widgets=["duration", "resolution"]),
|
||||||
|
expr="""
|
||||||
|
(
|
||||||
|
$is1080 := widgets.resolution = "1080p";
|
||||||
|
$base := $is1080 ? 0.1 : 0.075;
|
||||||
|
$perSec := $is1080 ? 0.05 : 0.025;
|
||||||
|
{"type":"usd","usd": $base + $perSec * (widgets.duration - 1)}
|
||||||
|
)
|
||||||
|
""",
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -594,6 +617,39 @@ class Vidu2ImageToVideoNode(IO.ComfyNode):
|
|||||||
IO.Hidden.unique_id,
|
IO.Hidden.unique_id,
|
||||||
],
|
],
|
||||||
is_api_node=True,
|
is_api_node=True,
|
||||||
|
price_badge=IO.PriceBadge(
|
||||||
|
depends_on=IO.PriceBadgeDepends(widgets=["model", "duration", "resolution"]),
|
||||||
|
expr="""
|
||||||
|
(
|
||||||
|
$m := widgets.model;
|
||||||
|
$d := widgets.duration;
|
||||||
|
$is1080 := widgets.resolution = "1080p";
|
||||||
|
$contains($m, "pro-fast")
|
||||||
|
? (
|
||||||
|
$base := $is1080 ? 0.08 : 0.04;
|
||||||
|
$perSec := $is1080 ? 0.02 : 0.01;
|
||||||
|
{"type":"usd","usd": $base + $perSec * ($d - 1)}
|
||||||
|
)
|
||||||
|
: $contains($m, "pro")
|
||||||
|
? (
|
||||||
|
$base := $is1080 ? 0.275 : 0.075;
|
||||||
|
$perSec := $is1080 ? 0.075 : 0.05;
|
||||||
|
{"type":"usd","usd": $base + $perSec * ($d - 1)}
|
||||||
|
)
|
||||||
|
: $contains($m, "turbo")
|
||||||
|
? (
|
||||||
|
$is1080
|
||||||
|
? {"type":"usd","usd": 0.175 + 0.05 * ($d - 1)}
|
||||||
|
: (
|
||||||
|
$d <= 1 ? {"type":"usd","usd": 0.04}
|
||||||
|
: $d <= 2 ? {"type":"usd","usd": 0.05}
|
||||||
|
: {"type":"usd","usd": 0.05 + 0.05 * ($d - 2)}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
: {"type":"usd","usd": 0.04}
|
||||||
|
)
|
||||||
|
""",
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -698,6 +754,18 @@ class Vidu2ReferenceVideoNode(IO.ComfyNode):
|
|||||||
IO.Hidden.unique_id,
|
IO.Hidden.unique_id,
|
||||||
],
|
],
|
||||||
is_api_node=True,
|
is_api_node=True,
|
||||||
|
price_badge=IO.PriceBadge(
|
||||||
|
depends_on=IO.PriceBadgeDepends(widgets=["audio", "duration", "resolution"]),
|
||||||
|
expr="""
|
||||||
|
(
|
||||||
|
$is1080 := widgets.resolution = "1080p";
|
||||||
|
$base := $is1080 ? 0.375 : 0.125;
|
||||||
|
$perSec := $is1080 ? 0.05 : 0.025;
|
||||||
|
$audioCost := widgets.audio = true ? 0.075 : 0;
|
||||||
|
{"type":"usd","usd": $base + $perSec * (widgets.duration - 1) + $audioCost}
|
||||||
|
)
|
||||||
|
""",
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -804,6 +872,38 @@ class Vidu2StartEndToVideoNode(IO.ComfyNode):
|
|||||||
IO.Hidden.unique_id,
|
IO.Hidden.unique_id,
|
||||||
],
|
],
|
||||||
is_api_node=True,
|
is_api_node=True,
|
||||||
|
price_badge=IO.PriceBadge(
|
||||||
|
depends_on=IO.PriceBadgeDepends(widgets=["model", "duration", "resolution"]),
|
||||||
|
expr="""
|
||||||
|
(
|
||||||
|
$m := widgets.model;
|
||||||
|
$d := widgets.duration;
|
||||||
|
$is1080 := widgets.resolution = "1080p";
|
||||||
|
$contains($m, "pro-fast")
|
||||||
|
? (
|
||||||
|
$base := $is1080 ? 0.08 : 0.04;
|
||||||
|
$perSec := $is1080 ? 0.02 : 0.01;
|
||||||
|
{"type":"usd","usd": $base + $perSec * ($d - 1)}
|
||||||
|
)
|
||||||
|
: $contains($m, "pro")
|
||||||
|
? (
|
||||||
|
$base := $is1080 ? 0.275 : 0.075;
|
||||||
|
$perSec := $is1080 ? 0.075 : 0.05;
|
||||||
|
{"type":"usd","usd": $base + $perSec * ($d - 1)}
|
||||||
|
)
|
||||||
|
: $contains($m, "turbo")
|
||||||
|
? (
|
||||||
|
$is1080
|
||||||
|
? {"type":"usd","usd": 0.175 + 0.05 * ($d - 1)}
|
||||||
|
: (
|
||||||
|
$d <= 2 ? {"type":"usd","usd": 0.05}
|
||||||
|
: {"type":"usd","usd": 0.05 + 0.05 * ($d - 2)}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
: {"type":"usd","usd": 0.04}
|
||||||
|
)
|
||||||
|
""",
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
|||||||
@ -244,6 +244,9 @@ class WanTextToImageApi(IO.ComfyNode):
|
|||||||
IO.Hidden.unique_id,
|
IO.Hidden.unique_id,
|
||||||
],
|
],
|
||||||
is_api_node=True,
|
is_api_node=True,
|
||||||
|
price_badge=IO.PriceBadge(
|
||||||
|
expr="""{"type":"usd","usd":0.03}""",
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -363,6 +366,9 @@ class WanImageToImageApi(IO.ComfyNode):
|
|||||||
IO.Hidden.unique_id,
|
IO.Hidden.unique_id,
|
||||||
],
|
],
|
||||||
is_api_node=True,
|
is_api_node=True,
|
||||||
|
price_badge=IO.PriceBadge(
|
||||||
|
expr="""{"type":"usd","usd":0.03}""",
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -520,6 +526,17 @@ class WanTextToVideoApi(IO.ComfyNode):
|
|||||||
IO.Hidden.unique_id,
|
IO.Hidden.unique_id,
|
||||||
],
|
],
|
||||||
is_api_node=True,
|
is_api_node=True,
|
||||||
|
price_badge=IO.PriceBadge(
|
||||||
|
depends_on=IO.PriceBadgeDepends(widgets=["duration", "size"]),
|
||||||
|
expr="""
|
||||||
|
(
|
||||||
|
$ppsTable := { "480p": 0.05, "720p": 0.1, "1080p": 0.15 };
|
||||||
|
$resKey := $substringBefore(widgets.size, ":");
|
||||||
|
$pps := $lookup($ppsTable, $resKey);
|
||||||
|
{ "type": "usd", "usd": $round($pps * widgets.duration, 2) }
|
||||||
|
)
|
||||||
|
""",
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -681,6 +698,16 @@ class WanImageToVideoApi(IO.ComfyNode):
|
|||||||
IO.Hidden.unique_id,
|
IO.Hidden.unique_id,
|
||||||
],
|
],
|
||||||
is_api_node=True,
|
is_api_node=True,
|
||||||
|
price_badge=IO.PriceBadge(
|
||||||
|
depends_on=IO.PriceBadgeDepends(widgets=["duration", "resolution"]),
|
||||||
|
expr="""
|
||||||
|
(
|
||||||
|
$ppsTable := { "480p": 0.05, "720p": 0.1, "1080p": 0.15 };
|
||||||
|
$pps := $lookup($ppsTable, widgets.resolution);
|
||||||
|
{ "type": "usd", "usd": $round($pps * widgets.duration, 2) }
|
||||||
|
)
|
||||||
|
""",
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -828,6 +855,22 @@ class WanReferenceVideoApi(IO.ComfyNode):
|
|||||||
IO.Hidden.unique_id,
|
IO.Hidden.unique_id,
|
||||||
],
|
],
|
||||||
is_api_node=True,
|
is_api_node=True,
|
||||||
|
price_badge=IO.PriceBadge(
|
||||||
|
depends_on=IO.PriceBadgeDepends(widgets=["size", "duration"]),
|
||||||
|
expr="""
|
||||||
|
(
|
||||||
|
$rate := $contains(widgets.size, "1080p") ? 0.15 : 0.10;
|
||||||
|
$inputMin := 2 * $rate;
|
||||||
|
$inputMax := 5 * $rate;
|
||||||
|
$outputPrice := widgets.duration * $rate;
|
||||||
|
{
|
||||||
|
"type": "range_usd",
|
||||||
|
"min_usd": $inputMin + $outputPrice,
|
||||||
|
"max_usd": $inputMax + $outputPrice
|
||||||
|
}
|
||||||
|
)
|
||||||
|
""",
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
|||||||
@ -244,6 +244,10 @@ class ModelPatchLoader:
|
|||||||
elif 'control_all_x_embedder.2-1.weight' in sd: # alipai z image fun controlnet
|
elif 'control_all_x_embedder.2-1.weight' in sd: # alipai z image fun controlnet
|
||||||
sd = z_image_convert(sd)
|
sd = z_image_convert(sd)
|
||||||
config = {}
|
config = {}
|
||||||
|
if 'control_layers.4.adaLN_modulation.0.weight' not in sd:
|
||||||
|
config['n_control_layers'] = 3
|
||||||
|
config['additional_in_dim'] = 17
|
||||||
|
config['refiner_control'] = True
|
||||||
if 'control_layers.14.adaLN_modulation.0.weight' in sd:
|
if 'control_layers.14.adaLN_modulation.0.weight' in sd:
|
||||||
config['n_control_layers'] = 15
|
config['n_control_layers'] = 15
|
||||||
config['additional_in_dim'] = 17
|
config['additional_in_dim'] = 17
|
||||||
|
|||||||
@ -1,3 +1,3 @@
|
|||||||
# This file is automatically generated by the build process when version is
|
# This file is automatically generated by the build process when version is
|
||||||
# updated in pyproject.toml.
|
# updated in pyproject.toml.
|
||||||
__version__ = "0.8.2"
|
__version__ = "0.9.1"
|
||||||
|
|||||||
4
nodes.py
4
nodes.py
@ -798,8 +798,8 @@ class VAELoader:
|
|||||||
vae_path = folder_paths.get_full_path_or_raise("vae_approx", vae_name)
|
vae_path = folder_paths.get_full_path_or_raise("vae_approx", vae_name)
|
||||||
else:
|
else:
|
||||||
vae_path = folder_paths.get_full_path_or_raise("vae", vae_name)
|
vae_path = folder_paths.get_full_path_or_raise("vae", vae_name)
|
||||||
sd = comfy.utils.load_torch_file(vae_path)
|
sd, metadata = comfy.utils.load_torch_file(vae_path, return_metadata=True)
|
||||||
vae = comfy.sd.VAE(sd=sd)
|
vae = comfy.sd.VAE(sd=sd, metadata=metadata)
|
||||||
vae.throw_exception_if_invalid()
|
vae.throw_exception_if_invalid()
|
||||||
return (vae,)
|
return (vae,)
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
[project]
|
[project]
|
||||||
name = "ComfyUI"
|
name = "ComfyUI"
|
||||||
version = "0.8.2"
|
version = "0.9.1"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
license = { file = "LICENSE" }
|
license = { file = "LICENSE" }
|
||||||
requires-python = ">=3.10"
|
requires-python = ">=3.10"
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
comfyui-frontend-package==1.36.13
|
comfyui-frontend-package==1.36.14
|
||||||
comfyui-workflow-templates==0.8.0
|
comfyui-workflow-templates==0.8.4
|
||||||
comfyui-embedded-docs==0.4.0
|
comfyui-embedded-docs==0.4.0
|
||||||
torch
|
torch
|
||||||
torchsde
|
torchsde
|
||||||
@ -21,7 +21,7 @@ psutil
|
|||||||
alembic
|
alembic
|
||||||
SQLAlchemy
|
SQLAlchemy
|
||||||
av>=14.2.0
|
av>=14.2.0
|
||||||
comfy-kitchen>=0.2.5
|
comfy-kitchen>=0.2.6
|
||||||
|
|
||||||
#non essential dependencies:
|
#non essential dependencies:
|
||||||
kornia>=0.7.1
|
kornia>=0.7.1
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user