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" ,
28+ "veo-3.1-fast-generate" : "veo-3.1-fast-generate" ,
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,12 @@ 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+ ),
282280 IO .String .Input (
283281 "negative_prompt" ,
284282 multiline = True ,
@@ -332,10 +330,10 @@ def define_schema(cls):
332330 options = [
333331 "veo-3.1-generate" ,
334332 "veo-3.1-fast-generate" ,
333+ "veo-3.1-lite" ,
335334 "veo-3.0-generate-001" ,
336335 "veo-3.0-fast-generate-001" ,
337336 ],
338- default = "veo-3.0-generate-001" ,
339337 tooltip = "Veo 3 model to use for video generation" ,
340338 optional = True ,
341339 ),
@@ -356,21 +354,111 @@ def define_schema(cls):
356354 ],
357355 is_api_node = True ,
358356 price_badge = IO .PriceBadge (
359- depends_on = IO .PriceBadgeDepends (widgets = ["model" , "generate_audio" ]),
357+ depends_on = IO .PriceBadgeDepends (widgets = ["model" , "generate_audio" , "resolution" , "duration_seconds" ]),
360358 expr = """
361359 (
362360 $m := widgets.model;
361+ $r := widgets.resolution;
363362 $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}
363+ $seconds := widgets.duration_seconds;
364+ $pps :=
365+ $contains($m, "lite")
366+ ? ($r = "1080p" ? ($a ? 0.08 : 0.05) : ($a ? 0.05 : 0.03))
367+ : $contains($m, "3.1-fast")
368+ ? ($r = "4k" ? ($a ? 0.30 : 0.25) : $r = "1080p" ? ($a ? 0.12 : 0.10) : ($a ? 0.10 : 0.08))
369+ : $contains($m, "3.1-generate")
370+ ? ($r = "4k" ? ($a ? 0.60 : 0.40) : ($a ? 0.40 : 0.20))
371+ : $contains($m, "3.0-fast")
372+ ? ($a ? 0.15 : 0.10)
373+ : ($a ? 0.40 : 0.20);
374+ {"type":"usd","usd": $pps * $seconds}
369375 )
370376 """ ,
371377 ),
372378 )
373379
380+ @classmethod
381+ async def execute (
382+ cls ,
383+ prompt ,
384+ aspect_ratio = "16:9" ,
385+ resolution = "720p" ,
386+ negative_prompt = "" ,
387+ duration_seconds = 8 ,
388+ enhance_prompt = True ,
389+ person_generation = "ALLOW" ,
390+ seed = 0 ,
391+ image = None ,
392+ model = "veo-3.0-generate-001" ,
393+ generate_audio = False ,
394+ ):
395+ if "lite" in model and resolution == "4k" :
396+ raise Exception ("4K resolution is not supported by the veo-3.1-lite model." )
397+
398+ model = MODELS_MAP [model ]
399+
400+ instances = [{"prompt" : prompt }]
401+ if image is not None :
402+ image_base64 = tensor_to_base64_string (image )
403+ if image_base64 :
404+ instances [0 ]["image" ] = {"bytesBase64Encoded" : image_base64 , "mimeType" : "image/png" }
405+
406+ parameters = {
407+ "aspectRatio" : aspect_ratio ,
408+ "personGeneration" : person_generation ,
409+ "durationSeconds" : duration_seconds ,
410+ "enhancePrompt" : True ,
411+ "generateAudio" : generate_audio ,
412+ }
413+ if negative_prompt :
414+ parameters ["negativePrompt" ] = negative_prompt
415+ if seed > 0 :
416+ parameters ["seed" ] = seed
417+ if "veo-3.1" in model :
418+ parameters ["resolution" ] = resolution
419+
420+ initial_response = await sync_op (
421+ cls ,
422+ ApiEndpoint (path = f"/proxy/veo/{ model } /generate" , method = "POST" ),
423+ response_model = VeoGenVidResponse ,
424+ data = VeoGenVidRequest (
425+ instances = instances ,
426+ parameters = parameters ,
427+ ),
428+ )
429+
430+ poll_response = await poll_op (
431+ cls ,
432+ ApiEndpoint (path = f"/proxy/veo/{ model } /poll" , method = "POST" ),
433+ response_model = VeoGenVidPollResponse ,
434+ status_extractor = lambda r : "completed" if r .done else "pending" ,
435+ data = VeoGenVidPollRequest (operationName = initial_response .name ),
436+ poll_interval = 5.0 ,
437+ estimated_duration = AVERAGE_DURATION_VIDEO_GEN ,
438+ )
439+
440+ if poll_response .error :
441+ raise Exception (f"Veo API error: { poll_response .error .message } (code: { poll_response .error .code } )" )
442+
443+ response = poll_response .response
444+ filtered_count = response .raiMediaFilteredCount
445+ if filtered_count :
446+ reasons = response .raiMediaFilteredReasons or []
447+ reason_part = f": { reasons [0 ]} " if reasons else ""
448+ raise Exception (
449+ f"Content blocked by Google's Responsible AI filters{ reason_part } "
450+ f"({ filtered_count } video{ 's' if filtered_count != 1 else '' } filtered)."
451+ )
452+
453+ if response .videos :
454+ video = response .videos [0 ]
455+ if video .bytesBase64Encoded :
456+ return IO .NodeOutput (InputImpl .VideoFromFile (BytesIO (base64 .b64decode (video .bytesBase64Encoded ))))
457+ if video .gcsUri :
458+ return IO .NodeOutput (await download_url_to_video_output (video .gcsUri ))
459+ raise Exception ("Video returned but no data or URL was provided" )
460+ raise Exception ("Video generation completed but no video was returned" )
461+
374462
375463class Veo3FirstLastFrameNode (IO .ComfyNode ):
376464
@@ -394,7 +482,7 @@ def define_schema(cls):
394482 default = "" ,
395483 tooltip = "Negative text prompt to guide what to avoid in the video" ,
396484 ),
397- IO .Combo .Input ("resolution" , options = ["720p" , "1080p" ]),
485+ IO .Combo .Input ("resolution" , options = ["720p" , "1080p" , "4k" ]),
398486 IO .Combo .Input (
399487 "aspect_ratio" ,
400488 options = ["16:9" , "9:16" ],
@@ -424,8 +512,7 @@ def define_schema(cls):
424512 IO .Image .Input ("last_frame" , tooltip = "End frame" ),
425513 IO .Combo .Input (
426514 "model" ,
427- options = ["veo-3.1-generate" , "veo-3.1-fast-generate" ],
428- default = "veo-3.1-fast-generate" ,
515+ options = ["veo-3.1-generate" , "veo-3.1-fast-generate" , "veo-3.1-lite" ],
429516 ),
430517 IO .Boolean .Input (
431518 "generate_audio" ,
@@ -443,26 +530,20 @@ def define_schema(cls):
443530 ],
444531 is_api_node = True ,
445532 price_badge = IO .PriceBadge (
446- depends_on = IO .PriceBadgeDepends (widgets = ["model" , "generate_audio" , "duration" ]),
533+ depends_on = IO .PriceBadgeDepends (widgets = ["model" , "generate_audio" , "duration" , "resolution" ]),
447534 expr = """
448535 (
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- };
453536 $m := widgets.model;
454- $ga := (widgets.generate_audio = "true");
537+ $r := widgets.resolution;
538+ $ga := widgets.generate_audio;
455539 $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}
540+ $pps :=
541+ $contains($m, "lite")
542+ ? ($r = "1080p" ? ($ga ? 0.08 : 0.05) : ($ga ? 0.05 : 0.03))
543+ : $contains($m, "fast")
544+ ? ($r = "4k" ? ($ga ? 0.30 : 0.25) : $r = "1080p" ? ($ga ? 0.12 : 0.10) : ($ga ? 0.10 : 0.08))
545+ : ($r = "4k" ? ($ga ? 0.60 : 0.40) : ($ga ? 0.40 : 0.20));
546+ {"type":"usd","usd": $pps * $seconds}
466547 )
467548 """ ,
468549 ),
@@ -482,6 +563,9 @@ async def execute(
482563 model : str ,
483564 generate_audio : bool ,
484565 ):
566+ if "lite" in model and resolution == "4k" :
567+ raise Exception ("4K resolution is not supported by the veo-3.1-lite model." )
568+
485569 model = MODELS_MAP [model ]
486570 initial_response = await sync_op (
487571 cls ,
0 commit comments