1919 Seedance2TaskCreationRequest ,
2020 SeedanceCreateAssetRequest ,
2121 SeedanceCreateAssetResponse ,
22+ SeedanceCreateVisualValidateSessionRequest ,
2223 SeedanceCreateVisualValidateSessionResponse ,
2324 SeedanceGetVisualValidateSessionResponse ,
2425 SeedanceVirtualLibraryCreateAssetRequest ,
@@ -196,11 +197,16 @@ def _sub(m: "re.Match[str]") -> str:
196197 return _ASSET_REF_RE .sub (_sub , prompt )
197198
198199
199- async def _obtain_group_id_via_h5_auth (cls : type [IO .ComfyNode ]) -> str :
200+ async def _obtain_group_id_via_h5_auth (
201+ cls : type [IO .ComfyNode ],
202+ group_name : str | None = None ,
203+ ) -> str :
204+ payload = SeedanceCreateVisualValidateSessionRequest (name = group_name )
200205 session = await sync_op (
201206 cls ,
202207 ApiEndpoint (path = "/proxy/seedance/visual-validate/sessions" , method = "POST" ),
203208 response_model = SeedanceCreateVisualValidateSessionResponse ,
209+ data = payload ,
204210 )
205211 logger .warning ("Seedance authentication required. Open link: %s" , session .h5_link )
206212
@@ -229,10 +235,15 @@ async def _obtain_group_id_via_h5_auth(cls: type[IO.ComfyNode]) -> str:
229235 return result .group_id
230236
231237
232- async def _resolve_group_id (cls : type [IO .ComfyNode ], group_id : str ) -> str :
238+ async def _resolve_group_id (
239+ cls : type [IO .ComfyNode ],
240+ group_id : str ,
241+ group_name : str | None = None ,
242+ ) -> str :
233243 if group_id and group_id .strip ():
234244 return group_id .strip ()
235- return await _obtain_group_id_via_h5_auth (cls )
245+ label = (group_name or "" ).strip () or None
246+ return await _obtain_group_id_via_h5_auth (cls , group_name = label )
236247
237248
238249async def _create_seedance_asset (
@@ -1936,6 +1947,43 @@ async def process_video_task(
19361947 return IO .NodeOutput (await download_url_to_video_output (response .content .video_url ))
19371948
19381949
1950+ def _seedance_group_picker_input () -> IO .Combo .Input :
1951+ """Combo populated from /proxy/seedance/groups. Empty selection triggers H5 enrollment."""
1952+ return IO .Combo .Input (
1953+ "group_id" ,
1954+ default = "" ,
1955+ tooltip = (
1956+ "Pick an existing verified group, or leave empty to run real-person H5 "
1957+ "authentication and create a new group."
1958+ ),
1959+ remote_combo = IO .RemoteComboOptions (
1960+ route = "/proxy/seedance/groups" ,
1961+ response_key = "groups" ,
1962+ item_schema = IO .RemoteItemSchema (
1963+ value_field = "group_id" ,
1964+ label_field = "name" ,
1965+ description_field = "created_at" ,
1966+ search_fields = ["name" , "group_id" ],
1967+ ),
1968+ refresh = 60_000 ,
1969+ ),
1970+ optional = True ,
1971+ )
1972+
1973+
1974+ def _seedance_group_name_input () -> IO .String .Input :
1975+ return IO .String .Input (
1976+ "group_name" ,
1977+ default = "" ,
1978+ tooltip = (
1979+ "Optional label for a new group. Used only when group_id is empty; the label is "
1980+ "shown later in the group picker so you can identify this group at a glance. "
1981+ "Up to 64 characters."
1982+ ),
1983+ optional = True ,
1984+ )
1985+
1986+
19391987class ByteDanceCreateImageAsset (IO .ComfyNode ):
19401988
19411989 @classmethod
@@ -1946,22 +1994,14 @@ def define_schema(cls) -> IO.Schema:
19461994 category = "api node/image/ByteDance" ,
19471995 description = (
19481996 "Create a Seedance 2.0 personal image asset. Uploads the input image and "
1949- "registers it in the given asset group. If group_id is empty, runs a real-person "
1950- "H5 authentication flow to create a new group before adding the asset."
1997+ "registers it in the selected asset group. Leave group_id empty to run a "
1998+ "real-person H5 authentication flow and create a new group; provide group_name "
1999+ "to label the new group."
19512000 ),
19522001 inputs = [
19532002 IO .Image .Input ("image" , tooltip = "Image to register as a personal asset." ),
1954- IO .String .Input (
1955- "group_id" ,
1956- default = "" ,
1957- tooltip = "Reuse an existing Seedance asset group ID to skip repeated human verification for the "
1958- "same person. Leave empty to run real-person authentication in the browser and create a new group." ,
1959- ),
1960- # IO.String.Input(
1961- # "name",
1962- # default="",
1963- # tooltip="Asset name (up to 64 characters).",
1964- # ),
2003+ _seedance_group_picker_input (),
2004+ _seedance_group_name_input (),
19652005 ],
19662006 outputs = [
19672007 IO .String .Output (display_name = "asset_id" ),
@@ -1980,13 +2020,11 @@ async def execute(
19802020 cls ,
19812021 image : Input .Image ,
19822022 group_id : str = "" ,
1983- # name : str = "",
2023+ group_name : str = "" ,
19842024 ) -> IO .NodeOutput :
1985- # if len(name) > 64:
1986- # raise ValueError("Name of asset can not be greater then 64 symbols")
19872025 validate_image_dimensions (image , min_width = 300 , max_width = 6000 , min_height = 300 , max_height = 6000 )
19882026 validate_image_aspect_ratio (image , min_ratio = (0.4 , 1 ), max_ratio = (2.5 , 1 ))
1989- resolved_group = await _resolve_group_id (cls , group_id )
2027+ resolved_group = await _resolve_group_id (cls , group_id , group_name = group_name )
19902028 asset_id = await _create_seedance_asset (
19912029 cls ,
19922030 group_id = resolved_group ,
@@ -2013,22 +2051,14 @@ def define_schema(cls) -> IO.Schema:
20132051 category = "api node/video/ByteDance" ,
20142052 description = (
20152053 "Create a Seedance 2.0 personal video asset. Uploads the input video and "
2016- "registers it in the given asset group. If group_id is empty, runs a real-person "
2017- "H5 authentication flow to create a new group before adding the asset."
2054+ "registers it in the selected asset group. Leave group_id empty to run a "
2055+ "real-person H5 authentication flow and create a new group; provide group_name "
2056+ "to label the new group."
20182057 ),
20192058 inputs = [
20202059 IO .Video .Input ("video" , tooltip = "Video to register as a personal asset." ),
2021- IO .String .Input (
2022- "group_id" ,
2023- default = "" ,
2024- tooltip = "Reuse an existing Seedance asset group ID to skip repeated human verification for the "
2025- "same person. Leave empty to run real-person authentication in the browser and create a new group." ,
2026- ),
2027- # IO.String.Input(
2028- # "name",
2029- # default="",
2030- # tooltip="Asset name (up to 64 characters).",
2031- # ),
2060+ _seedance_group_picker_input (),
2061+ _seedance_group_name_input (),
20322062 ],
20332063 outputs = [
20342064 IO .String .Output (display_name = "asset_id" ),
@@ -2047,10 +2077,8 @@ async def execute(
20472077 cls ,
20482078 video : Input .Video ,
20492079 group_id : str = "" ,
2050- # name : str = "",
2080+ group_name : str = "" ,
20512081 ) -> IO .NodeOutput :
2052- # if len(name) > 64:
2053- # raise ValueError("Name of asset can not be greater then 64 symbols")
20542082 validate_video_duration (video , min_duration = 2 , max_duration = 15 )
20552083 validate_video_dimensions (video , min_width = 300 , max_width = 6000 , min_height = 300 , max_height = 6000 )
20562084
@@ -2069,7 +2097,7 @@ async def execute(
20692097 if not (24 <= fps <= 60 ):
20702098 raise ValueError (f"Asset video FPS must be in [24, 60], got { fps :.2f} ." )
20712099
2072- resolved_group = await _resolve_group_id (cls , group_id )
2100+ resolved_group = await _resolve_group_id (cls , group_id , group_name = group_name )
20732101 asset_id = await _create_seedance_asset (
20742102 cls ,
20752103 group_id = resolved_group ,
@@ -2086,6 +2114,92 @@ async def execute(
20862114 return IO .NodeOutput (asset_id , resolved_group )
20872115
20882116
2117+ def _seedance_asset_picker_input (asset_type : str , preview_type : str ) -> IO .Combo .Input :
2118+ """Combo populated from /proxy/seedance/assets, scoped to one asset_type."""
2119+ return IO .Combo .Input (
2120+ "asset_id" ,
2121+ tooltip = (
2122+ f"Pick a previously-created Seedance { asset_type .lower ()} asset. The dropdown shows "
2123+ "your assets across all your verified groups; type a group name to filter."
2124+ ),
2125+ remote_combo = IO .RemoteComboOptions (
2126+ route = f"/proxy/seedance/assets?asset_type={ asset_type } " ,
2127+ response_key = "assets" ,
2128+ item_schema = IO .RemoteItemSchema (
2129+ value_field = "asset_id" ,
2130+ label_field = "name" ,
2131+ description_field = "group_name" ,
2132+ preview_url_field = "url" ,
2133+ preview_type = preview_type ,
2134+ search_fields = ["name" , "asset_id" , "group_name" , "group_id" ],
2135+ ),
2136+ refresh = 60_000 ,
2137+ ),
2138+ )
2139+
2140+
2141+ class ByteDanceSelectImageAsset (IO .ComfyNode ):
2142+
2143+ @classmethod
2144+ def define_schema (cls ) -> IO .Schema :
2145+ return IO .Schema (
2146+ node_id = "ByteDanceSelectImageAsset" ,
2147+ display_name = "ByteDance Select Image Asset" ,
2148+ category = "api node/image/ByteDance" ,
2149+ description = (
2150+ "Pick a previously-created Seedance image asset. Outputs the selected asset_id "
2151+ "for use with downstream Seedance 2.0 reference/first-last-frame nodes."
2152+ ),
2153+ inputs = [
2154+ _seedance_asset_picker_input ("Image" , "image" ),
2155+ ],
2156+ outputs = [IO .String .Output (display_name = "asset_id" )],
2157+ hidden = [
2158+ IO .Hidden .auth_token_comfy_org ,
2159+ IO .Hidden .api_key_comfy_org ,
2160+ IO .Hidden .unique_id ,
2161+ ],
2162+ # is_api_node=True,
2163+ )
2164+
2165+ @classmethod
2166+ async def execute (cls , asset_id : str ) -> IO .NodeOutput :
2167+ if not asset_id or not asset_id .strip ():
2168+ raise ValueError ("asset_id is required. Pick an asset from the dropdown." )
2169+ return IO .NodeOutput (asset_id .strip ())
2170+
2171+
2172+ class ByteDanceSelectVideoAsset (IO .ComfyNode ):
2173+
2174+ @classmethod
2175+ def define_schema (cls ) -> IO .Schema :
2176+ return IO .Schema (
2177+ node_id = "ByteDanceSelectVideoAsset" ,
2178+ display_name = "ByteDance Select Video Asset" ,
2179+ category = "api node/video/ByteDance" ,
2180+ description = (
2181+ "Pick a previously-created Seedance video asset. Outputs the selected asset_id "
2182+ "for use with downstream Seedance 2.0 reference/first-last-frame nodes."
2183+ ),
2184+ inputs = [
2185+ _seedance_asset_picker_input ("Video" , "video" ),
2186+ ],
2187+ outputs = [IO .String .Output (display_name = "asset_id" )],
2188+ hidden = [
2189+ IO .Hidden .auth_token_comfy_org ,
2190+ IO .Hidden .api_key_comfy_org ,
2191+ IO .Hidden .unique_id ,
2192+ ],
2193+ # is_api_node=True,
2194+ )
2195+
2196+ @classmethod
2197+ async def execute (cls , asset_id : str ) -> IO .NodeOutput :
2198+ if not asset_id or not asset_id .strip ():
2199+ raise ValueError ("asset_id is required. Pick an asset from the dropdown." )
2200+ return IO .NodeOutput (asset_id .strip ())
2201+
2202+
20892203class ByteDanceExtension (ComfyExtension ):
20902204 @override
20912205 async def get_node_list (self ) -> list [type [IO .ComfyNode ]]:
@@ -2101,6 +2215,8 @@ async def get_node_list(self) -> list[type[IO.ComfyNode]]:
21012215 ByteDance2ReferenceNode ,
21022216 ByteDanceCreateImageAsset ,
21032217 ByteDanceCreateVideoAsset ,
2218+ ByteDanceSelectImageAsset ,
2219+ ByteDanceSelectVideoAsset ,
21042220 ]
21052221
21062222
0 commit comments