@@ -54,6 +54,7 @@ async def execute_task(
5454 response_model = TaskStatusResponse ,
5555 status_extractor = lambda r : r .state ,
5656 progress_extractor = lambda r : r .progress ,
57+ price_extractor = lambda r : r .credits * 0.005 if r .credits is not None else None ,
5758 max_poll_attempts = max_poll_attempts ,
5859 )
5960 if not response .creations :
@@ -1306,6 +1307,36 @@ def define_schema(cls):
13061307 ),
13071308 ],
13081309 ),
1310+ IO .DynamicCombo .Option (
1311+ "viduq3-turbo" ,
1312+ [
1313+ IO .Combo .Input (
1314+ "aspect_ratio" ,
1315+ options = ["16:9" , "9:16" , "3:4" , "4:3" , "1:1" ],
1316+ tooltip = "The aspect ratio of the output video." ,
1317+ ),
1318+ IO .Combo .Input (
1319+ "resolution" ,
1320+ options = ["720p" , "1080p" ],
1321+ tooltip = "Resolution of the output video." ,
1322+ ),
1323+ IO .Int .Input (
1324+ "duration" ,
1325+ default = 5 ,
1326+ min = 1 ,
1327+ max = 16 ,
1328+ step = 1 ,
1329+ display_mode = IO .NumberDisplay .slider ,
1330+ tooltip = "Duration of the output video in seconds." ,
1331+ ),
1332+ IO .Boolean .Input (
1333+ "audio" ,
1334+ default = False ,
1335+ tooltip = "When enabled, outputs video with sound "
1336+ "(including dialogue and sound effects)." ,
1337+ ),
1338+ ],
1339+ ),
13091340 ],
13101341 tooltip = "Model to use for video generation." ,
13111342 ),
@@ -1334,13 +1365,20 @@ def define_schema(cls):
13341365 ],
13351366 is_api_node = True ,
13361367 price_badge = IO .PriceBadge (
1337- depends_on = IO .PriceBadgeDepends (widgets = ["model.duration" , "model.resolution" ]),
1368+ depends_on = IO .PriceBadgeDepends (widgets = ["model" , "model .duration" , "model.resolution" ]),
13381369 expr = """
13391370 (
13401371 $res := $lookup(widgets, "model.resolution");
1341- $base := $lookup({"720p": 0.075, "1080p": 0.1}, $res);
1342- $perSec := $lookup({"720p": 0.025, "1080p": 0.05}, $res);
1343- {"type":"usd","usd": $base + $perSec * ($lookup(widgets, "model.duration") - 1)}
1372+ $d := $lookup(widgets, "model.duration");
1373+ $contains(widgets.model, "turbo")
1374+ ? (
1375+ $rate := $lookup({"720p": 0.06, "1080p": 0.08}, $res);
1376+ {"type":"usd","usd": $rate * $d}
1377+ )
1378+ : (
1379+ $rate := $lookup({"720p": 0.15, "1080p": 0.16}, $res);
1380+ {"type":"usd","usd": $rate * $d}
1381+ )
13441382 )
13451383 """ ,
13461384 ),
@@ -1409,6 +1447,31 @@ def define_schema(cls):
14091447 ),
14101448 ],
14111449 ),
1450+ IO .DynamicCombo .Option (
1451+ "viduq3-turbo" ,
1452+ [
1453+ IO .Combo .Input (
1454+ "resolution" ,
1455+ options = ["720p" , "1080p" ],
1456+ tooltip = "Resolution of the output video." ,
1457+ ),
1458+ IO .Int .Input (
1459+ "duration" ,
1460+ default = 5 ,
1461+ min = 1 ,
1462+ max = 16 ,
1463+ step = 1 ,
1464+ display_mode = IO .NumberDisplay .slider ,
1465+ tooltip = "Duration of the output video in seconds." ,
1466+ ),
1467+ IO .Boolean .Input (
1468+ "audio" ,
1469+ default = False ,
1470+ tooltip = "When enabled, outputs video with sound "
1471+ "(including dialogue and sound effects)." ,
1472+ ),
1473+ ],
1474+ ),
14121475 ],
14131476 tooltip = "Model to use for video generation." ,
14141477 ),
@@ -1442,13 +1505,20 @@ def define_schema(cls):
14421505 ],
14431506 is_api_node = True ,
14441507 price_badge = IO .PriceBadge (
1445- depends_on = IO .PriceBadgeDepends (widgets = ["model.duration" , "model.resolution" ]),
1508+ depends_on = IO .PriceBadgeDepends (widgets = ["model" , "model .duration" , "model.resolution" ]),
14461509 expr = """
14471510 (
14481511 $res := $lookup(widgets, "model.resolution");
1449- $base := $lookup({"720p": 0.075, "1080p": 0.275, "2k": 0.35}, $res);
1450- $perSec := $lookup({"720p": 0.05, "1080p": 0.075, "2k": 0.075}, $res);
1451- {"type":"usd","usd": $base + $perSec * ($lookup(widgets, "model.duration") - 1)}
1512+ $d := $lookup(widgets, "model.duration");
1513+ $contains(widgets.model, "turbo")
1514+ ? (
1515+ $rate := $lookup({"720p": 0.06, "1080p": 0.08}, $res);
1516+ {"type":"usd","usd": $rate * $d}
1517+ )
1518+ : (
1519+ $rate := $lookup({"720p": 0.15, "1080p": 0.16, "2k": 0.2}, $res);
1520+ {"type":"usd","usd": $rate * $d}
1521+ )
14521522 )
14531523 """ ,
14541524 ),
@@ -1481,6 +1551,145 @@ async def execute(
14811551 return IO .NodeOutput (await download_url_to_video_output (results [0 ].url ))
14821552
14831553
1554+ class Vidu3StartEndToVideoNode (IO .ComfyNode ):
1555+
1556+ @classmethod
1557+ def define_schema (cls ):
1558+ return IO .Schema (
1559+ node_id = "Vidu3StartEndToVideoNode" ,
1560+ display_name = "Vidu Q3 Start/End Frame-to-Video Generation" ,
1561+ category = "api node/video/Vidu" ,
1562+ description = "Generate a video from a start frame, an end frame, and a prompt." ,
1563+ inputs = [
1564+ IO .DynamicCombo .Input (
1565+ "model" ,
1566+ options = [
1567+ IO .DynamicCombo .Option (
1568+ "viduq3-pro" ,
1569+ [
1570+ IO .Combo .Input (
1571+ "resolution" ,
1572+ options = ["720p" , "1080p" ],
1573+ tooltip = "Resolution of the output video." ,
1574+ ),
1575+ IO .Int .Input (
1576+ "duration" ,
1577+ default = 5 ,
1578+ min = 1 ,
1579+ max = 16 ,
1580+ step = 1 ,
1581+ display_mode = IO .NumberDisplay .slider ,
1582+ tooltip = "Duration of the output video in seconds." ,
1583+ ),
1584+ IO .Boolean .Input (
1585+ "audio" ,
1586+ default = False ,
1587+ tooltip = "When enabled, outputs video with sound "
1588+ "(including dialogue and sound effects)." ,
1589+ ),
1590+ ],
1591+ ),
1592+ IO .DynamicCombo .Option (
1593+ "viduq3-turbo" ,
1594+ [
1595+ IO .Combo .Input (
1596+ "resolution" ,
1597+ options = ["720p" , "1080p" ],
1598+ tooltip = "Resolution of the output video." ,
1599+ ),
1600+ IO .Int .Input (
1601+ "duration" ,
1602+ default = 5 ,
1603+ min = 1 ,
1604+ max = 16 ,
1605+ step = 1 ,
1606+ display_mode = IO .NumberDisplay .slider ,
1607+ tooltip = "Duration of the output video in seconds." ,
1608+ ),
1609+ IO .Boolean .Input (
1610+ "audio" ,
1611+ default = False ,
1612+ tooltip = "When enabled, outputs video with sound "
1613+ "(including dialogue and sound effects)." ,
1614+ ),
1615+ ],
1616+ ),
1617+ ],
1618+ tooltip = "Model to use for video generation." ,
1619+ ),
1620+ IO .Image .Input ("first_frame" ),
1621+ IO .Image .Input ("end_frame" ),
1622+ IO .String .Input (
1623+ "prompt" ,
1624+ multiline = True ,
1625+ tooltip = "Prompt description (max 2000 characters)." ,
1626+ ),
1627+ IO .Int .Input (
1628+ "seed" ,
1629+ default = 1 ,
1630+ min = 0 ,
1631+ max = 2147483647 ,
1632+ step = 1 ,
1633+ display_mode = IO .NumberDisplay .number ,
1634+ control_after_generate = True ,
1635+ ),
1636+ ],
1637+ outputs = [
1638+ IO .Video .Output (),
1639+ ],
1640+ hidden = [
1641+ IO .Hidden .auth_token_comfy_org ,
1642+ IO .Hidden .api_key_comfy_org ,
1643+ IO .Hidden .unique_id ,
1644+ ],
1645+ is_api_node = True ,
1646+ price_badge = IO .PriceBadge (
1647+ depends_on = IO .PriceBadgeDepends (widgets = ["model" , "model.duration" , "model.resolution" ]),
1648+ expr = """
1649+ (
1650+ $res := $lookup(widgets, "model.resolution");
1651+ $d := $lookup(widgets, "model.duration");
1652+ $contains(widgets.model, "turbo")
1653+ ? (
1654+ $rate := $lookup({"720p": 0.06, "1080p": 0.08}, $res);
1655+ {"type":"usd","usd": $rate * $d}
1656+ )
1657+ : (
1658+ $rate := $lookup({"720p": 0.15, "1080p": 0.16}, $res);
1659+ {"type":"usd","usd": $rate * $d}
1660+ )
1661+ )
1662+ """ ,
1663+ ),
1664+ )
1665+
1666+ @classmethod
1667+ async def execute (
1668+ cls ,
1669+ model : dict ,
1670+ first_frame : Input .Image ,
1671+ end_frame : Input .Image ,
1672+ prompt : str ,
1673+ seed : int ,
1674+ ) -> IO .NodeOutput :
1675+ validate_string (prompt , max_length = 2000 )
1676+ validate_images_aspect_ratio_closeness (first_frame , end_frame , min_rel = 0.8 , max_rel = 1.25 , strict = False )
1677+ payload = TaskCreationRequest (
1678+ model = model ["model" ],
1679+ prompt = prompt ,
1680+ duration = model ["duration" ],
1681+ seed = seed ,
1682+ resolution = model ["resolution" ],
1683+ audio = model ["audio" ],
1684+ images = [
1685+ (await upload_images_to_comfyapi (cls , frame , max_images = 1 , mime_type = "image/png" ))[0 ]
1686+ for frame in (first_frame , end_frame )
1687+ ],
1688+ )
1689+ results = await execute_task (cls , VIDU_START_END_VIDEO , payload )
1690+ return IO .NodeOutput (await download_url_to_video_output (results [0 ].url ))
1691+
1692+
14841693class ViduExtension (ComfyExtension ):
14851694 @override
14861695 async def get_node_list (self ) -> list [type [IO .ComfyNode ]]:
@@ -1497,6 +1706,7 @@ async def get_node_list(self) -> list[type[IO.ComfyNode]]:
14971706 ViduMultiFrameVideoNode ,
14981707 Vidu3TextToVideoNode ,
14991708 Vidu3ImageToVideoNode ,
1709+ Vidu3StartEndToVideoNode ,
15001710 ]
15011711
15021712
0 commit comments