55from pydantic import BaseModel , Field
66from typing_extensions import override
77
8- from comfy_api .input_impl import VideoFromFile
9- from comfy_api .latest import IO , ComfyExtension
8+ from comfy_api .latest import IO , ComfyExtension , Input , InputImpl
109from comfy_api_nodes .util import (
1110 ApiEndpoint ,
1211 get_number_of_images ,
1312 sync_op_raw ,
1413 upload_images_to_comfyapi ,
1514 validate_string ,
15+ validate_video_duration ,
16+ validate_video_dimensions ,
17+ validate_video_frame_count ,
18+ upload_video_to_comfyapi ,
1619)
1720
1821MODELS_MAP = {
@@ -31,6 +34,14 @@ class ExecuteTaskRequest(BaseModel):
3134 image_uri : Optional [str ] = Field (None )
3235
3336
37+ class VideoEditRequest (BaseModel ):
38+ video_uri : str = Field (...)
39+ prompt : str = Field (...)
40+ start_time : int = Field (...)
41+ duration : int = Field (...)
42+ mode : str = Field (...)
43+
44+
3445class TextToVideoNode (IO .ComfyNode ):
3546 @classmethod
3647 def define_schema (cls ):
@@ -103,7 +114,7 @@ async def execute(
103114 as_binary = True ,
104115 max_retries = 1 ,
105116 )
106- return IO .NodeOutput (VideoFromFile (BytesIO (response )))
117+ return IO .NodeOutput (InputImpl . VideoFromFile (BytesIO (response )))
107118
108119
109120class ImageToVideoNode (IO .ComfyNode ):
@@ -183,7 +194,76 @@ async def execute(
183194 as_binary = True ,
184195 max_retries = 1 ,
185196 )
186- return IO .NodeOutput (VideoFromFile (BytesIO (response )))
197+ return IO .NodeOutput (InputImpl .VideoFromFile (BytesIO (response )))
198+
199+
200+ class EditVideoNode (IO .ComfyNode ):
201+ @classmethod
202+ def define_schema (cls ):
203+ return IO .Schema (
204+ node_id = "LtxvApiEditVideoNode" ,
205+ display_name = "LTXV Video To Video" ,
206+ category = "api node/video/LTXV" ,
207+ description = "Edit a specific section of a video by replacing audio, video, or both using AI generation." ,
208+ inputs = [
209+ IO .Video .Input ("video" ),
210+ IO .String .Input (
211+ "prompt" ,
212+ multiline = True ,
213+ default = "" ,
214+ ),
215+ IO .Combo .Input ("mode" , options = ["replace_video" , "replace_audio" , "replace_audio_and_video" ]),
216+ IO .Float .Input ("start_time" , min = 0.0 , default = 0.0 ),
217+ IO .Float .Input ("duration" , min = 1.0 , max = 20.0 , default = 3 ),
218+ ],
219+ outputs = [
220+ IO .Video .Output (),
221+ ],
222+ hidden = [
223+ IO .Hidden .auth_token_comfy_org ,
224+ IO .Hidden .api_key_comfy_org ,
225+ IO .Hidden .unique_id ,
226+ ],
227+ is_api_node = True ,
228+ )
229+
230+ @classmethod
231+ async def execute (
232+ cls ,
233+ video : Input .Video ,
234+ prompt : str ,
235+ mode : str ,
236+ start_time : float ,
237+ duration : float ,
238+ ) -> IO .NodeOutput :
239+ validate_string (prompt , min_length = 1 , max_length = 10000 )
240+ validate_video_dimensions (video , max_width = 3840 , max_height = 2160 )
241+ validate_video_duration (video , max_duration = 20 )
242+ validate_video_frame_count (video , max_frame_count = 505 )
243+ video_duration = video .get_duration ()
244+ if start_time >= video_duration :
245+ raise ValueError (
246+ f"Invalid start_time ({ start_time } ). Start time is greater than input video duration ({ video_duration } )"
247+ )
248+ response = await sync_op_raw (
249+ cls ,
250+ # ApiEndpoint(
251+ # "https://api.ltx.video/v1/retake",
252+ # "POST",
253+ # headers={"Authorization": "Bearer PLACE_YOUR_API_KEY"},
254+ # ),
255+ ApiEndpoint ("/proxy/ltx/v1/retake" , "POST" ),
256+ data = VideoEditRequest (
257+ video_uri = await upload_video_to_comfyapi (cls , video ),
258+ prompt = prompt ,
259+ mode = mode ,
260+ start_time = int (start_time ),
261+ duration = int (duration ),
262+ ),
263+ as_binary = True ,
264+ max_retries = 1 ,
265+ )
266+ return IO .NodeOutput (InputImpl .VideoFromFile (BytesIO (response )))
187267
188268
189269class LtxvApiExtension (ComfyExtension ):
@@ -192,6 +272,7 @@ async def get_node_list(self) -> list[type[IO.ComfyNode]]:
192272 return [
193273 TextToVideoNode ,
194274 ImageToVideoNode ,
275+ EditVideoNode ,
195276 ]
196277
197278
0 commit comments