Skip to content

Commit 341ea77

Browse files
matthew29tangcopybara-github
authored andcommitted
feat: Support video extension for Veo on Gemini Developer API
PiperOrigin-RevId: 819532215
1 parent 160997e commit 341ea77

3 files changed

Lines changed: 179 additions & 16 deletions

File tree

google/genai/_operations_converters.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -201,11 +201,11 @@ def _GeneratedVideo_from_mldev(
201201
parent_object: Optional[dict[str, Any]] = None,
202202
) -> dict[str, Any]:
203203
to_object: dict[str, Any] = {}
204-
if getv(from_object, ['_self']) is not None:
204+
if getv(from_object, ['video']) is not None:
205205
setv(
206206
to_object,
207207
['video'],
208-
_Video_from_mldev(getv(from_object, ['_self']), to_object),
208+
_Video_from_mldev(getv(from_object, ['video']), to_object),
209209
)
210210

211211
return to_object
@@ -261,14 +261,14 @@ def _Video_from_mldev(
261261
parent_object: Optional[dict[str, Any]] = None,
262262
) -> dict[str, Any]:
263263
to_object: dict[str, Any] = {}
264-
if getv(from_object, ['video', 'uri']) is not None:
265-
setv(to_object, ['uri'], getv(from_object, ['video', 'uri']))
264+
if getv(from_object, ['uri']) is not None:
265+
setv(to_object, ['uri'], getv(from_object, ['uri']))
266266

267-
if getv(from_object, ['video', 'encodedVideo']) is not None:
267+
if getv(from_object, ['encodedVideo']) is not None:
268268
setv(
269269
to_object,
270270
['video_bytes'],
271-
base_t.t_bytes(getv(from_object, ['video', 'encodedVideo'])),
271+
base_t.t_bytes(getv(from_object, ['encodedVideo'])),
272272
)
273273

274274
if getv(from_object, ['encoding']) is not None:

google/genai/models.py

Lines changed: 38 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2262,11 +2262,11 @@ def _GeneratedVideo_from_mldev(
22622262
parent_object: Optional[dict[str, Any]] = None,
22632263
) -> dict[str, Any]:
22642264
to_object: dict[str, Any] = {}
2265-
if getv(from_object, ['_self']) is not None:
2265+
if getv(from_object, ['video']) is not None:
22662266
setv(
22672267
to_object,
22682268
['video'],
2269-
_Video_from_mldev(getv(from_object, ['_self']), to_object),
2269+
_Video_from_mldev(getv(from_object, ['video']), to_object),
22702270
)
22712271

22722272
return to_object
@@ -3680,14 +3680,14 @@ def _Video_from_mldev(
36803680
parent_object: Optional[dict[str, Any]] = None,
36813681
) -> dict[str, Any]:
36823682
to_object: dict[str, Any] = {}
3683-
if getv(from_object, ['video', 'uri']) is not None:
3684-
setv(to_object, ['uri'], getv(from_object, ['video', 'uri']))
3683+
if getv(from_object, ['uri']) is not None:
3684+
setv(to_object, ['uri'], getv(from_object, ['uri']))
36853685

3686-
if getv(from_object, ['video', 'encodedVideo']) is not None:
3686+
if getv(from_object, ['encodedVideo']) is not None:
36873687
setv(
36883688
to_object,
36893689
['video_bytes'],
3690-
base_t.t_bytes(getv(from_object, ['video', 'encodedVideo'])),
3690+
base_t.t_bytes(getv(from_object, ['encodedVideo'])),
36913691
)
36923692

36933693
if getv(from_object, ['encoding']) is not None:
@@ -3723,12 +3723,12 @@ def _Video_to_mldev(
37233723
) -> dict[str, Any]:
37243724
to_object: dict[str, Any] = {}
37253725
if getv(from_object, ['uri']) is not None:
3726-
setv(to_object, ['video', 'uri'], getv(from_object, ['uri']))
3726+
setv(to_object, ['uri'], getv(from_object, ['uri']))
37273727

37283728
if getv(from_object, ['video_bytes']) is not None:
37293729
setv(
37303730
to_object,
3731-
['video', 'encodedVideo'],
3731+
['encodedVideo'],
37323732
base_t.t_bytes(getv(from_object, ['video_bytes'])),
37333733
)
37343734

@@ -5448,6 +5448,36 @@ def generate_videos(
54485448
'Source and prompt/image/video are mutually exclusive.'
54495449
+ ' Please only use source.'
54505450
)
5451+
# Gemini Developer API does not support video bytes.
5452+
video_dct: dict[str, Any] = {}
5453+
if not self._api_client.vertexai and video:
5454+
if isinstance(video, types.Video):
5455+
video_dct = video.model_dump()
5456+
else:
5457+
video_dct = dict(video)
5458+
5459+
if video_dct.get('uri') and video_dct.get('video_bytes'):
5460+
video = types.Video(
5461+
uri=video_dct.get('uri'), mime_type=video_dct.get('mime_type')
5462+
)
5463+
elif not self._api_client.vertexai and source:
5464+
if isinstance(source, types.GenerateVideosSource):
5465+
source_dct = source.model_dump()
5466+
video_dct = source_dct.get('video', {})
5467+
else:
5468+
source_dct = dict(source)
5469+
if isinstance(source_dct.get('video'), types.Video):
5470+
video_obj: types.Video = source_dct.get('video', types.Video())
5471+
video_dct = video_obj.model_dump()
5472+
if video_dct and video_dct.get('uri') and video_dct.get('video_bytes'):
5473+
source = types.GenerateVideosSource(
5474+
prompt=source_dct.get('prompt'),
5475+
image=source_dct.get('image'),
5476+
video=types.Video(
5477+
uri=video_dct.get('uri'),
5478+
mime_type=video_dct.get('mime_type'),
5479+
),
5480+
)
54515481
return self._generate_videos(
54525482
model=model,
54535483
prompt=prompt,

google/genai/tests/models/test_generate_videos.py

Lines changed: 135 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -310,7 +310,7 @@ def test_text_and_image_to_video_poll(client):
310310

311311

312312
def test_video_to_video_poll(client):
313-
# Video extension is only supported in Vertex AI.
313+
# GCS URI video input is only supported in Vertex AI.
314314
if not client.vertexai:
315315
return
316316

@@ -333,7 +333,7 @@ def test_video_to_video_poll(client):
333333

334334

335335
def test_text_and_video_to_video_poll(client):
336-
# Video extension is only supported in Vertex AI.
336+
# GCS URI video input is only supported in Vertex AI.
337337
if not client.vertexai:
338338
return
339339

@@ -356,6 +356,139 @@ def test_text_and_video_to_video_poll(client):
356356
assert operation.result.generated_videos[0].video.uri
357357

358358

359+
def test_generated_video_extension_poll(client):
360+
# Gemini API only supports video extension on generated videos.
361+
if client.vertexai:
362+
return
363+
364+
operation1 = client.models.generate_videos(
365+
model="veo-3-exp",
366+
prompt="Rain",
367+
config=types.GenerateVideosConfig(
368+
number_of_videos=1,
369+
),
370+
)
371+
while not operation1.done:
372+
# Skip the sleep when in replay mode.
373+
if client._api_client._mode not in ("replay", "auto"):
374+
time.sleep(20)
375+
operation1 = client.operations.get(operation=operation1)
376+
377+
video1 = operation1.result.generated_videos[0].video
378+
assert video1.uri
379+
client.files.download(file=video1)
380+
assert video1.video_bytes
381+
382+
operation2 = client.models.generate_videos(
383+
model="veo-3-exp",
384+
prompt="Sun",
385+
video=video1,
386+
config=types.GenerateVideosConfig(
387+
number_of_videos=1,
388+
),
389+
)
390+
while not operation2.done:
391+
# Skip the sleep when in replay mode.
392+
if client._api_client._mode not in ("replay", "auto"):
393+
time.sleep(20)
394+
operation2 = client.operations.get(operation=operation2)
395+
396+
video2 = operation2.result.generated_videos[0].video
397+
assert video2.uri
398+
client.files.download(file=video2)
399+
assert video2.video_bytes
400+
401+
402+
def test_generated_video_extension_from_source_poll(client):
403+
# Gemini API only supports video extension on generated videos.
404+
if client.vertexai:
405+
return
406+
407+
operation1 = client.models.generate_videos(
408+
model="veo-3-exp",
409+
prompt="Rain",
410+
config=types.GenerateVideosConfig(
411+
number_of_videos=1,
412+
),
413+
)
414+
while not operation1.done:
415+
# Skip the sleep when in replay mode.
416+
if client._api_client._mode not in ("replay", "auto"):
417+
time.sleep(20)
418+
operation1 = client.operations.get(operation=operation1)
419+
420+
video1 = operation1.result.generated_videos[0].video
421+
assert video1.uri
422+
client.files.download(file=video1)
423+
assert video1.video_bytes
424+
425+
operation2 = client.models.generate_videos(
426+
model="veo-3-exp",
427+
source=types.GenerateVideosSource(
428+
prompt="Sun",
429+
video=video1
430+
),
431+
config=types.GenerateVideosConfig(
432+
number_of_videos=1,
433+
),
434+
)
435+
while not operation2.done:
436+
# Skip the sleep when in replay mode.
437+
if client._api_client._mode not in ("replay", "auto"):
438+
time.sleep(20)
439+
operation2 = client.operations.get(operation=operation2)
440+
441+
video2 = operation2.result.generated_videos[0].video
442+
assert video2.uri
443+
client.files.download(file=video2)
444+
assert video2.video_bytes
445+
446+
447+
def test_generated_video_extension_from_source_dict_poll(client):
448+
# Gemini API only supports video extension on generated videos.
449+
if client.vertexai:
450+
return
451+
452+
operation1 = client.models.generate_videos(
453+
model="veo-3-exp",
454+
prompt="Rain",
455+
config=types.GenerateVideosConfig(
456+
number_of_videos=1,
457+
),
458+
)
459+
while not operation1.done:
460+
# Skip the sleep when in replay mode.
461+
if client._api_client._mode not in ("replay", "auto"):
462+
time.sleep(20)
463+
operation1 = client.operations.get(operation=operation1)
464+
465+
video1 = operation1.result.generated_videos[0].video
466+
assert video1.uri
467+
client.files.download(file=video1)
468+
assert video1.video_bytes
469+
470+
operation2 = client.models.generate_videos(
471+
model="veo-3-exp",
472+
source={
473+
"prompt": "Sun",
474+
"video": video1,
475+
},
476+
config=types.GenerateVideosConfig(
477+
number_of_videos=1,
478+
),
479+
)
480+
while not operation2.done:
481+
# Skip the sleep when in replay mode.
482+
if client._api_client._mode not in ("replay", "auto"):
483+
time.sleep(20)
484+
operation2 = client.operations.get(operation=operation2)
485+
486+
video2 = operation2.result.generated_videos[0].video
487+
assert video2.uri
488+
client.files.download(file=video2)
489+
assert video2.video_bytes
490+
491+
359492
def test_image_to_video_frame_interpolation_poll(client):
360493
operation = client.models.generate_videos(
361494
model="veo-2.0-generate-exp" if client.vertexai else "veo-3-exp",

0 commit comments

Comments
 (0)