2424AVERAGE_DURATION_VIDEO_GEN = 32
2525MODELS_MAP = {
2626 "veo-2.0-generate-001" : "veo-2.0-generate-001" ,
27- "veo-3.1-generate" : "veo-3.1-generate-preview" ,
28- "veo-3.1-fast-generate" : "veo-3.1-fast-generate-preview" ,
27+ "veo-3.1-generate" : "veo-3.1-generate-001" ,
28+ "veo-3.1-fast-generate" : "veo-3.1-fast-generate-001" ,
29+ "veo-3.1-lite" : "veo-3.1-lite-generate-001" ,
2930 "veo-3.0-generate-001" : "veo-3.0-generate-001" ,
3031 "veo-3.0-fast-generate-001" : "veo-3.0-fast-generate-001" ,
3132}
@@ -247,17 +248,8 @@ def status_extractor(response):
247248 raise Exception ("Video generation completed but no video was returned" )
248249
249250
250- class Veo3VideoGenerationNode (VeoVideoGenerationNode ):
251- """
252- Generates videos from text prompts using Google's Veo 3 API.
253-
254- Supported models:
255- - veo-3.0-generate-001
256- - veo-3.0-fast-generate-001
257-
258- This node extends the base Veo node with Veo 3 specific features including
259- audio generation and fixed 8-second duration.
260- """
251+ class Veo3VideoGenerationNode (IO .ComfyNode ):
252+ """Generates videos from text prompts using Google's Veo 3 API."""
261253
262254 @classmethod
263255 def define_schema (cls ):
@@ -279,6 +271,13 @@ def define_schema(cls):
279271 default = "16:9" ,
280272 tooltip = "Aspect ratio of the output video" ,
281273 ),
274+ IO .Combo .Input (
275+ "resolution" ,
276+ options = ["720p" , "1080p" , "4k" ],
277+ default = "720p" ,
278+ tooltip = "Output video resolution. 4K is not available for veo-3.1-lite and veo-3.0 models." ,
279+ optional = True ,
280+ ),
282281 IO .String .Input (
283282 "negative_prompt" ,
284283 multiline = True ,
@@ -289,11 +288,11 @@ def define_schema(cls):
289288 IO .Int .Input (
290289 "duration_seconds" ,
291290 default = 8 ,
292- min = 8 ,
291+ min = 4 ,
293292 max = 8 ,
294- step = 1 ,
293+ step = 2 ,
295294 display_mode = IO .NumberDisplay .number ,
296- tooltip = "Duration of the output video in seconds (Veo 3 only supports 8 seconds) " ,
295+ tooltip = "Duration of the output video in seconds" ,
297296 optional = True ,
298297 ),
299298 IO .Boolean .Input (
@@ -332,10 +331,10 @@ def define_schema(cls):
332331 options = [
333332 "veo-3.1-generate" ,
334333 "veo-3.1-fast-generate" ,
334+ "veo-3.1-lite" ,
335335 "veo-3.0-generate-001" ,
336336 "veo-3.0-fast-generate-001" ,
337337 ],
338- default = "veo-3.0-generate-001" ,
339338 tooltip = "Veo 3 model to use for video generation" ,
340339 optional = True ,
341340 ),
@@ -356,21 +355,111 @@ def define_schema(cls):
356355 ],
357356 is_api_node = True ,
358357 price_badge = IO .PriceBadge (
359- depends_on = IO .PriceBadgeDepends (widgets = ["model" , "generate_audio" ]),
358+ depends_on = IO .PriceBadgeDepends (widgets = ["model" , "generate_audio" , "resolution" , "duration_seconds" ]),
360359 expr = """
361360 (
362361 $m := widgets.model;
362+ $r := widgets.resolution;
363363 $a := widgets.generate_audio;
364- ($contains($m,"veo-3.0-fast-generate-001") or $contains($m,"veo-3.1-fast-generate"))
365- ? {"type":"usd","usd": ($a ? 1.2 : 0.8)}
366- : ($contains($m,"veo-3.0-generate-001") or $contains($m,"veo-3.1-generate"))
367- ? {"type":"usd","usd": ($a ? 3.2 : 1.6)}
368- : {"type":"range_usd","min_usd":0.8,"max_usd":3.2}
364+ $seconds := widgets.duration_seconds;
365+ $pps :=
366+ $contains($m, "lite")
367+ ? ($r = "1080p" ? ($a ? 0.08 : 0.05) : ($a ? 0.05 : 0.03))
368+ : $contains($m, "3.1-fast")
369+ ? ($r = "4k" ? ($a ? 0.30 : 0.25) : $r = "1080p" ? ($a ? 0.12 : 0.10) : ($a ? 0.10 : 0.08))
370+ : $contains($m, "3.1-generate")
371+ ? ($r = "4k" ? ($a ? 0.60 : 0.40) : ($a ? 0.40 : 0.20))
372+ : $contains($m, "3.0-fast")
373+ ? ($a ? 0.15 : 0.10)
374+ : ($a ? 0.40 : 0.20);
375+ {"type":"usd","usd": $pps * $seconds}
369376 )
370377 """ ,
371378 ),
372379 )
373380
381+ @classmethod
382+ async def execute (
383+ cls ,
384+ prompt ,
385+ aspect_ratio = "16:9" ,
386+ resolution = "720p" ,
387+ negative_prompt = "" ,
388+ duration_seconds = 8 ,
389+ enhance_prompt = True ,
390+ person_generation = "ALLOW" ,
391+ seed = 0 ,
392+ image = None ,
393+ model = "veo-3.0-generate-001" ,
394+ generate_audio = False ,
395+ ):
396+ if "lite" in model and resolution == "4k" :
397+ raise Exception ("4K resolution is not supported by the veo-3.1-lite model." )
398+
399+ model = MODELS_MAP [model ]
400+
401+ instances = [{"prompt" : prompt }]
402+ if image is not None :
403+ image_base64 = tensor_to_base64_string (image )
404+ if image_base64 :
405+ instances [0 ]["image" ] = {"bytesBase64Encoded" : image_base64 , "mimeType" : "image/png" }
406+
407+ parameters = {
408+ "aspectRatio" : aspect_ratio ,
409+ "personGeneration" : person_generation ,
410+ "durationSeconds" : duration_seconds ,
411+ "enhancePrompt" : True ,
412+ "generateAudio" : generate_audio ,
413+ }
414+ if negative_prompt :
415+ parameters ["negativePrompt" ] = negative_prompt
416+ if seed > 0 :
417+ parameters ["seed" ] = seed
418+ if "veo-3.1" in model :
419+ parameters ["resolution" ] = resolution
420+
421+ initial_response = await sync_op (
422+ cls ,
423+ ApiEndpoint (path = f"/proxy/veo/{ model } /generate" , method = "POST" ),
424+ response_model = VeoGenVidResponse ,
425+ data = VeoGenVidRequest (
426+ instances = instances ,
427+ parameters = parameters ,
428+ ),
429+ )
430+
431+ poll_response = await poll_op (
432+ cls ,
433+ ApiEndpoint (path = f"/proxy/veo/{ model } /poll" , method = "POST" ),
434+ response_model = VeoGenVidPollResponse ,
435+ status_extractor = lambda r : "completed" if r .done else "pending" ,
436+ data = VeoGenVidPollRequest (operationName = initial_response .name ),
437+ poll_interval = 5.0 ,
438+ estimated_duration = AVERAGE_DURATION_VIDEO_GEN ,
439+ )
440+
441+ if poll_response .error :
442+ raise Exception (f"Veo API error: { poll_response .error .message } (code: { poll_response .error .code } )" )
443+
444+ response = poll_response .response
445+ filtered_count = response .raiMediaFilteredCount
446+ if filtered_count :
447+ reasons = response .raiMediaFilteredReasons or []
448+ reason_part = f": { reasons [0 ]} " if reasons else ""
449+ raise Exception (
450+ f"Content blocked by Google's Responsible AI filters{ reason_part } "
451+ f"({ filtered_count } video{ 's' if filtered_count != 1 else '' } filtered)."
452+ )
453+
454+ if response .videos :
455+ video = response .videos [0 ]
456+ if video .bytesBase64Encoded :
457+ return IO .NodeOutput (InputImpl .VideoFromFile (BytesIO (base64 .b64decode (video .bytesBase64Encoded ))))
458+ if video .gcsUri :
459+ return IO .NodeOutput (await download_url_to_video_output (video .gcsUri ))
460+ raise Exception ("Video returned but no data or URL was provided" )
461+ raise Exception ("Video generation completed but no video was returned" )
462+
374463
375464class Veo3FirstLastFrameNode (IO .ComfyNode ):
376465
@@ -394,7 +483,7 @@ def define_schema(cls):
394483 default = "" ,
395484 tooltip = "Negative text prompt to guide what to avoid in the video" ,
396485 ),
397- IO .Combo .Input ("resolution" , options = ["720p" , "1080p" ]),
486+ IO .Combo .Input ("resolution" , options = ["720p" , "1080p" , "4k" ]),
398487 IO .Combo .Input (
399488 "aspect_ratio" ,
400489 options = ["16:9" , "9:16" ],
@@ -424,8 +513,7 @@ def define_schema(cls):
424513 IO .Image .Input ("last_frame" , tooltip = "End frame" ),
425514 IO .Combo .Input (
426515 "model" ,
427- options = ["veo-3.1-generate" , "veo-3.1-fast-generate" ],
428- default = "veo-3.1-fast-generate" ,
516+ options = ["veo-3.1-generate" , "veo-3.1-fast-generate" , "veo-3.1-lite" ],
429517 ),
430518 IO .Boolean .Input (
431519 "generate_audio" ,
@@ -443,26 +531,20 @@ def define_schema(cls):
443531 ],
444532 is_api_node = True ,
445533 price_badge = IO .PriceBadge (
446- depends_on = IO .PriceBadgeDepends (widgets = ["model" , "generate_audio" , "duration" ]),
534+ depends_on = IO .PriceBadgeDepends (widgets = ["model" , "generate_audio" , "duration" , "resolution" ]),
447535 expr = """
448536 (
449- $prices := {
450- "veo-3.1-fast-generate": { "audio": 0.15, "no_audio": 0.10 },
451- "veo-3.1-generate": { "audio": 0.40, "no_audio": 0.20 }
452- };
453537 $m := widgets.model;
454- $ga := (widgets.generate_audio = "true");
538+ $r := widgets.resolution;
539+ $ga := widgets.generate_audio;
455540 $seconds := widgets.duration;
456- $modelKey :=
457- $contains($m, "veo-3.1-fast-generate") ? "veo-3.1-fast-generate" :
458- $contains($m, "veo-3.1-generate") ? "veo-3.1-generate" :
459- "";
460- $audioKey := $ga ? "audio" : "no_audio";
461- $modelPrices := $lookup($prices, $modelKey);
462- $pps := $lookup($modelPrices, $audioKey);
463- ($pps != null)
464- ? {"type":"usd","usd": $pps * $seconds}
465- : {"type":"range_usd","min_usd": 0.4, "max_usd": 3.2}
541+ $pps :=
542+ $contains($m, "lite")
543+ ? ($r = "1080p" ? ($ga ? 0.08 : 0.05) : ($ga ? 0.05 : 0.03))
544+ : $contains($m, "fast")
545+ ? ($r = "4k" ? ($ga ? 0.30 : 0.25) : $r = "1080p" ? ($ga ? 0.12 : 0.10) : ($ga ? 0.10 : 0.08))
546+ : ($r = "4k" ? ($ga ? 0.60 : 0.40) : ($ga ? 0.40 : 0.20));
547+ {"type":"usd","usd": $pps * $seconds}
466548 )
467549 """ ,
468550 ),
@@ -482,6 +564,9 @@ async def execute(
482564 model : str ,
483565 generate_audio : bool ,
484566 ):
567+ if "lite" in model and resolution == "4k" :
568+ raise Exception ("4K resolution is not supported by the veo-3.1-lite model." )
569+
485570 model = MODELS_MAP [model ]
486571 initial_response = await sync_op (
487572 cls ,
0 commit comments