Skip to content

Commit aba8129

Browse files
committed
api nodes: added 4K resolution for Veo models; added Veo 3 Lite model
Signed-off-by: bigcat88 <bigcat88@icloud.com>
1 parent b615af1 commit aba8129

1 file changed

Lines changed: 123 additions & 39 deletions

File tree

comfy_api_nodes/nodes_veo2.py

Lines changed: 123 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,9 @@
2424
AVERAGE_DURATION_VIDEO_GEN = 32
2525
MODELS_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

375463
class 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

Comments
 (0)