Script 'mail_helper' called by obssrc
Hello community,
here is the log from the commit of package python-xai-sdk for openSUSE:Factory
checked in at 2026-09-28 10:39:02
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-xai-sdk (Old)
and /work/SRC/openSUSE:Factory/.python-xai-sdk.new.383539 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-xai-sdk"
Mon Sep 28 10:39:02 2026 rev:4 rq:1380711 version:1.20.0
Changes:
--------
--- /work/SRC/openSUSE:Factory/python-xai-sdk/python-xai-sdk.changes
2026-08-19 18:01:22.246562891 +0200
+++
/work/SRC/openSUSE:Factory/.python-xai-sdk.new.383539/python-xai-sdk.changes
2026-09-28 10:40:20.360584735 +0200
@@ -1,0 +2,8 @@
+Fri Sep 25 17:14:04 UTC 2026 - Martin Pluskal <[email protected]>
+
+- Update to version 1.20.0:
+ * Add last frame and keyframes to video generation
+ * Keep protobuf-7 compatibility patch (upstream still
+ gates v6 stubs on major == 6)
+
+-------------------------------------------------------------------
Old:
----
xai_sdk-1.19.0.tar.gz
New:
----
xai_sdk-1.20.0.tar.gz
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Other differences:
------------------
++++++ python-xai-sdk.spec ++++++
--- /var/tmp/diff_new_pack.wUmVZM/_old 2026-09-28 10:40:20.987611028 +0200
+++ /var/tmp/diff_new_pack.wUmVZM/_new 2026-09-28 10:40:20.988611070 +0200
@@ -17,7 +17,7 @@
Name: python-xai-sdk
-Version: 1.19.0
+Version: 1.20.0
Release: 0
Summary: The official Python SDK for the xAI API
License: Apache-2.0
++++++ xai_sdk-1.19.0.tar.gz -> xai_sdk-1.20.0.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/xai_sdk-1.19.0/CHANGELOG.md
new/xai_sdk-1.20.0/CHANGELOG.md
--- old/xai_sdk-1.19.0/CHANGELOG.md 2020-02-02 01:00:00.000000000 +0100
+++ new/xai_sdk-1.20.0/CHANGELOG.md 2020-02-02 01:00:00.000000000 +0100
@@ -2,19 +2,18 @@
## [Unreleased]
### Added
+- Added first & last frame pinning to video generation. `last_frame_url` /
`last_frame_file_id` (on `client.video.generate` / `start` / `prepare`) pin the
exact last frame of the clip; combined with `image_url` / `image_file_id`, the
video interpolates between a pinned first and last frame. `image_url` /
`image_file_id` combined with reference inputs, a last frame, or keyframes now
pins the exact first frame. Only supported for `grok-imagine-video-1.5`.
+- Added a `keyframes` parameter to video generation (`generate` / `start` /
`prepare`) that pins images at chosen moments inside the clip. Each entry is a
TypedDict such as `{"image_url": "https://...", "timestamp": 2.0}` or
`{"image_file_id": "file_...", "timestamp": 2.0}`, with `timestamp` in seconds;
timestamps must fall strictly inside the clip, at most four entries. Only
supported for `grok-imagine-video-1.5`.
+
+## [v1.19.0](https://github.com/xai-org/xai-sdk-python/releases/tag/v1.19.0) -
2026-08-18
+### Added
- Set a default gRPC User-Agent of `XaiSdk/{version}` via
`grpc.primary_user_agent` (same string already used for image URL fetches;
exposed as `xai_sdk.client.USER_AGENT`)
- Added `reference_audios` and `generate_audio` parameters to video
generation. `reference_audios` (on `client.video.generate` / `start` /
`prepare`) is a list of audio sources for reference-to-video — each entry is a
TypedDict such as `{"voice_id": "ara"}`; only supported for
`grok-imagine-video-1.5`. `generate_audio` (on `generate` / `start` /
`prepare`) controls whether the generated video includes audio (`true`) or is
silent (`false`); when omitted the server defaults to `true`.
- Added `"1080p"` as an accepted video `resolution` value (maps to
`VIDEO_RESOLUTION_1080P`). Only supported on models that advertise 1080p (e.g.
`grok-imagine-video-1.5` for image-to-video).
- Added a `quality` parameter (`"low"`, `"medium"`) to image generation
(`client.image.sample`, `sample_batch`, and batch `prepare`), mapping to the
`GenerateImageRequest.quality` field. When omitted, the default is `"medium"`.
Only supported for `grok-imagine-image-2.0`.
- Added `grok-imagine-image-2.0` to the `ImageGenerationModel` known-model
type literal
-- **`xhigh` Reasoning Effort**: Added `"xhigh"` as an accepted
`reasoning_effort` value (maps to `EFFORT_XHIGH`; supported by models such as
`grok-4.6`)
-- Added `grok-4.6` to the `ChatModel` known-model type literal
- **Image Generation Tool**: Added `xai_sdk.tools.image_generation` helper for
the server-side `image_generation` tool, enabling image generation and editing
in agentic requests. Accepts an optional `action` parameter (`"auto"`,
`"generate"`, or `"edit"`) to control which image capabilities are exposed to
the model
- **Image Generation Tool Outputs**: Added `Response.image_outputs` for
retrieving images generated by the server-side `image_generation` tool as
decoded bytes (`output.image`), along with `mime_type`, `data_url`,
`image_uuid`, and the originating `tool_call`
-- **Imagine File Storage**: Image and video generation (sync and async) now
accept a `storage_options` parameter to persist generated assets to the Files
API. It takes a dict with a required `filename` and optional `expires_after`
(an `int` in seconds or a `datetime.timedelta`) and `public_url` (`True` to
create a public URL with default expiry, or `{"expires_after": <seconds>}` for
an independent URL expiry). Image and video responses expose new `file_output`,
`storage_error`, `public_url`, and `public_url_error` properties.
-- **File-ID Inputs for Generation**: Image and video generation now accept
Files API `file_id` references as inputs alongside URLs/base64 —
`image_file_id` / `image_file_ids` for `image.sample()` /
`image.sample_batch()`, and `image_file_id` / `video_file_id` /
`reference_image_file_ids` for `video.generate()` / `video.extend()` (and the
batch `prepare` helpers). URL and file-ID lists may be mixed in the same
multi-image request (file IDs are sent first).
-- **Public File URLs**: Added `client.files.create_public_url()` and
`client.files.revoke_public_url()` (sync and async) to create and revoke
publicly shareable, unauthenticated URLs for stored files.
`create_public_url()` accepts an optional `expires_after` (an `int` in seconds
or a `datetime.timedelta`).
-- **Files List Filter**: `client.files.list()` (sync and async) now accepts an
optional `filter` parameter to narrow results server-side by fields such as
`content_type`, `size_bytes`, `created_at`, `upload_status`, and `public_url`
(e.g. `filter='public_url != null'`).
### Changed
- Model type literals now match the current public model catalog: added
`grok-imagine-video-1.5` and removed `grok-imagine-video-1.5-preview` (now an
alias of `grok-imagine-video-1.5`) from `VideoGenerationModel`, and removed
`grok-imagine-image-pro` (an alias of `grok-imagine-image-quality`) from
`ImageGenerationModel`. These literals are type hints for editor autocomplete
only — alias strings continue to work when passed to the API.
@@ -22,14 +21,48 @@
### Fixed
- `chat.append(response)` now sets `tool_call_id` on replayed tool-role
messages (recovered from the tool call echoed on the output), so servers can
pair replayed tool turns with their originating calls. This fixes stateless
multi-turn follow-ups to server-side tools whose results are re-hydrated by ID
— e.g. editing a previously generated image in-memory without
`previous_response_id`.
-## [v1.14.0]
+## [v1.18.0](https://github.com/xai-org/xai-sdk-python/releases/tag/v1.18.0) -
2026-08-13
+### Added
+- **`xhigh` Reasoning Effort**: Added `"xhigh"` as an accepted
`reasoning_effort` value (maps to `EFFORT_XHIGH`; supported by models such as
`grok-4.6`)
+- Added `grok-4.6` to the `ChatModel` known-model type literal
+
+## [v1.17.1](https://github.com/xai-org/xai-sdk-python/releases/tag/v1.17.1) -
2026-07-14
+### Added
+- Added `grok-4.5` and `grok-4.5-latest` to the `ChatModel` known-model type
literal
+
+## [v1.17.0](https://github.com/xai-org/xai-sdk-python/releases/tag/v1.17.0) -
2026-06-12
+### Added
+- **Service Tier**: Added `service_tier` parameter to `chat.create()` for
requesting priority processing (`"default"` or `"priority"`) at a higher token
price. Responses expose a `service_tier` property indicating which tier was
actually used
+
+## [v1.16.0](https://github.com/xai-org/xai-sdk-python/releases/tag/v1.16.0) -
2026-06-10
+### Added
+- **Imagine File Storage**: Image and video generation (sync and async) now
accept a `storage_options` parameter to persist generated assets to the Files
API. It takes a dict with a required `filename` and optional `expires_after`
(an `int` in seconds or a `datetime.timedelta`) and `public_url` (`True` to
create a public URL with default expiry, or `{"expires_after": <seconds>}` for
an independent URL expiry). Image and video responses expose new `file_output`,
`storage_error`, `public_url`, and `public_url_error` properties.
+- **File-ID Inputs for Generation**: Image and video generation now accept
Files API `file_id` references as inputs alongside URLs/base64 —
`image_file_id` / `image_file_ids` for `image.sample()` /
`image.sample_batch()`, and `image_file_id` / `video_file_id` /
`reference_image_file_ids` for `video.generate()` / `video.extend()` (and the
batch `prepare` helpers). URL and file-ID lists may be mixed in the same
multi-image request (file IDs are sent first).
+- **Public File URLs**: Added `client.files.create_public_url()` and
`client.files.revoke_public_url()` (sync and async) to create and revoke
publicly shareable, unauthenticated URLs for stored files.
`create_public_url()` accepts an optional `expires_after` (an `int` in seconds
or a `datetime.timedelta`).
+- **Files List Filter**: `client.files.list()` (sync and async) now accepts an
optional `filter` parameter to narrow results server-side by fields such as
`content_type`, `size_bytes`, `created_at`, `upload_status`, and `public_url`
(e.g. `filter='public_url != null'`).
+- Added `grok-imagine-image-quality` to the `ImageGenerationModel` known-model
type literal and `grok-imagine-video-1.5-preview` to the `VideoGenerationModel`
known-model type literal
+
+## [v1.15.0](https://github.com/xai-org/xai-sdk-python/releases/tag/v1.15.0) -
2026-05-30
+### Added
+- **Context Compaction**: Added `chat.compact_context()` (sync and async) to
compact a conversation into an opaque `encrypted_content` representation that
can be reused in follow-up requests without sending the full message history.
Chat objects also expose `compact()`, which runs compaction on the current
conversation and appends the result. The returned `CompactContextResponse`
includes `encrypted_content`, `dropped_message_count`, and `usage`.
+- Added `grok-build-0.1` to the `ChatModel` known-model type literal
+
+## [v1.14.0](https://github.com/xai-org/xai-sdk-python/releases/tag/v1.14.0) -
2026-05-27
### Added
- **Image Search**: Added `enable_image_search` parameter to `web_search()` to
return image results that can be embedded in responses
-## [v1.13.0]
+## [v1.13.0](https://github.com/xai-org/xai-sdk-python/releases/tag/v1.13.0) -
2026-05-16
### Added
- **Batch Video Extension**: Added `video.prepare_extension()` to sync and
async clients for preparing video extension requests for the Batch API
+## [v1.12.2](https://github.com/xai-org/xai-sdk-python/releases/tag/v1.12.2) -
2026-05-07
+### Added
+- **Reasoning Effort**: Added `"none"` and `"medium"` as accepted
`reasoning_effort` values (maps to `EFFORT_NONE` and `EFFORT_MEDIUM`)
+
+## [v1.12.1](https://github.com/xai-org/xai-sdk-python/releases/tag/v1.12.1) -
2026-04-30
+### Added
+- Added `grok-4.3` and `grok-4.3-latest` to the `ChatModel` known-model type
literal
+
## [v1.12.0](https://github.com/xai-org/xai-sdk-python/releases/tag/v1.12.0) -
2026-04-29
### Added
- **Cost Tracking**: Responses from `chat.sample()` / `chat.stream()`,
`image.sample()` / `image.sample_batch()`, and `video.generate()` /
`video.extend()` now expose a `cost_usd` property that returns the per-request
cost in USD (or `None` when the server does not report cost)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/xai_sdk-1.19.0/PKG-INFO new/xai_sdk-1.20.0/PKG-INFO
--- old/xai_sdk-1.19.0/PKG-INFO 2020-02-02 01:00:00.000000000 +0100
+++ new/xai_sdk-1.20.0/PKG-INFO 2020-02-02 01:00:00.000000000 +0100
@@ -1,6 +1,6 @@
Metadata-Version: 2.5
Name: xai-sdk
-Version: 1.19.0
+Version: 1.20.0
Summary: The official Python SDK for the xAI API
Project-URL: Homepage, https://github.com/xai-org/xai-sdk-python
Project-URL: Documentation, https://docs.x.ai
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/xai_sdk-1.19.0/examples/aio/video_keyframes.py
new/xai_sdk-1.20.0/examples/aio/video_keyframes.py
--- old/xai_sdk-1.19.0/examples/aio/video_keyframes.py 1970-01-01
01:00:00.000000000 +0100
+++ new/xai_sdk-1.20.0/examples/aio/video_keyframes.py 2020-02-02
01:00:00.000000000 +0100
@@ -0,0 +1,88 @@
+"""Four seasons in one request: a pinned first frame, keyframes and a pinned
last frame together.
+
+Generates a winter oak with an image model, edits that still into spring,
summer and autumn, then
+pins all four in a single video request: winter as the exact first frame,
spring and summer as
+keyframes, and autumn as the exact last frame. Editing one source image keeps
the tree, field and
+camera angle consistent, so the video moves smoothly from pin to pin. Pinned
frames require
+grok-imagine-video-1.5.
+
+Example:
+ uv run examples/aio/video_keyframes.py
+ uv run examples/aio/video_keyframes.py --duration 12 --resolution 480p
+"""
+
+import asyncio
+from typing import Sequence, cast
+
+from absl import app, flags
+
+import xai_sdk
+from xai_sdk.types import VideoResolution
+
+MODEL = flags.DEFINE_string(
+ "model", "grok-imagine-video-1.5", "Video model; pinned frames need
grok-imagine-video-1.5."
+)
+IMAGE_MODEL = flags.DEFINE_string("image-model", "grok-imagine-image", "Image
model used to create the stills.")
+DURATION = flags.DEFINE_integer("duration", 9, "Clip length in seconds (2-15).
Keyframes are spaced evenly.")
+RESOLUTION = flags.DEFINE_enum(
+ "resolution", "720p", ["480p", "720p"], "Video resolution. 1080p is
unavailable once a frame is pinned."
+)
+
+WINTER_PROMPT = (
+ "Photorealistic wide landscape: a lone oak tree on a gentle rise in the
middle of a ploughed field in "
+ "late winter, bare branches against an overcast sky, the tree centered,
camera at eye level."
+)
+KEEP_SCENE = "Keep the exact same tree, field, horizon and camera angle."
+SEASON_EDITS = {
+ "spring": f"Spring: fresh green leaves and white blossom on the tree,
green shoots in the field. {KEEP_SCENE}",
+ "summer": f"High summer: a dense dark-green canopy, a golden wheat field,
blue sky with cumulus. {KEEP_SCENE}",
+ "autumn": f"Autumn: blazing orange and red leaves, a few drifting down,
low golden-hour sun. {KEEP_SCENE}",
+}
+VIDEO_PROMPT = (
+ "A locked-off time-lapse of a single oak across a year: buds burst into
blossom, the canopy thickens "
+ "through summer, then blazes orange in autumn while clouds race overhead"
+)
+
+
+async def main(argv: Sequence[str]) -> None:
+ if len(argv) > 1:
+ raise app.UsageError("Unexpected command line arguments.")
+
+ client = xai_sdk.AsyncClient()
+
+ winter = await client.image.sample(WINTER_PROMPT, model=IMAGE_MODEL.value,
aspect_ratio="16:9", image_format="url")
+ stills = {"winter": winter.url}
+ edits = await asyncio.gather(
+ *(
+ client.image.sample(prompt, model=IMAGE_MODEL.value,
image_url=stills["winter"], image_format="url")
+ for prompt in SEASON_EDITS.values()
+ )
+ )
+ stills.update({season: edited.url for season, edited in zip(SEASON_EDITS,
edits, strict=True)})
+ for season, url in stills.items():
+ print(f"{season}: {url}")
+
+ duration = DURATION.value
+ print(f"Rendering a {duration}s clip...")
+ response = await client.video.generate(
+ prompt=VIDEO_PROMPT,
+ model=MODEL.value,
+ # Alongside keyframes or a last frame, `image_url` pins the exact
first frame.
+ image_url=stills["winter"],
+ keyframes=[
+ {"image_url": stills["spring"], "timestamp": duration / 3},
+ {"image_url": stills["summer"], "timestamp": 2 * duration / 3},
+ ],
+ last_frame_url=stills["autumn"],
+ duration=duration,
+ aspect_ratio="16:9",
+ resolution=cast(VideoResolution, RESOLUTION.value),
+ )
+
+ print(f"Video URL: {response.url}")
+ if response.cost_usd is not None:
+ print(f"Cost in USD: ${response.cost_usd:.2f}")
+
+
+if __name__ == "__main__":
+ app.run(lambda argv: asyncio.run(main(argv)))
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/xai_sdk-1.19.0/examples/sync/video_keyframes.py
new/xai_sdk-1.20.0/examples/sync/video_keyframes.py
--- old/xai_sdk-1.19.0/examples/sync/video_keyframes.py 1970-01-01
01:00:00.000000000 +0100
+++ new/xai_sdk-1.20.0/examples/sync/video_keyframes.py 2020-02-02
01:00:00.000000000 +0100
@@ -0,0 +1,83 @@
+"""Four seasons in one request: a pinned first frame, keyframes and a pinned
last frame together.
+
+Generates a winter oak with an image model, edits that still into spring,
summer and autumn, then
+pins all four in a single video request: winter as the exact first frame,
spring and summer as
+keyframes, and autumn as the exact last frame. Editing one source image keeps
the tree, field and
+camera angle consistent, so the video moves smoothly from pin to pin. Pinned
frames require
+grok-imagine-video-1.5.
+
+Example:
+ uv run examples/sync/video_keyframes.py
+ uv run examples/sync/video_keyframes.py --duration 12 --resolution 480p
+"""
+
+from typing import Sequence, cast
+
+from absl import app, flags
+
+import xai_sdk
+from xai_sdk.types import VideoResolution
+
+MODEL = flags.DEFINE_string(
+ "model", "grok-imagine-video-1.5", "Video model; pinned frames need
grok-imagine-video-1.5."
+)
+IMAGE_MODEL = flags.DEFINE_string("image-model", "grok-imagine-image", "Image
model used to create the stills.")
+DURATION = flags.DEFINE_integer("duration", 9, "Clip length in seconds (2-15).
Keyframes are spaced evenly.")
+RESOLUTION = flags.DEFINE_enum(
+ "resolution", "720p", ["480p", "720p"], "Video resolution. 1080p is
unavailable once a frame is pinned."
+)
+
+WINTER_PROMPT = (
+ "Photorealistic wide landscape: a lone oak tree on a gentle rise in the
middle of a ploughed field in "
+ "late winter, bare branches against an overcast sky, the tree centered,
camera at eye level."
+)
+KEEP_SCENE = "Keep the exact same tree, field, horizon and camera angle."
+SEASON_EDITS = {
+ "spring": f"Spring: fresh green leaves and white blossom on the tree,
green shoots in the field. {KEEP_SCENE}",
+ "summer": f"High summer: a dense dark-green canopy, a golden wheat field,
blue sky with cumulus. {KEEP_SCENE}",
+ "autumn": f"Autumn: blazing orange and red leaves, a few drifting down,
low golden-hour sun. {KEEP_SCENE}",
+}
+VIDEO_PROMPT = (
+ "A locked-off time-lapse of a single oak across a year: buds burst into
blossom, the canopy thickens "
+ "through summer, then blazes orange in autumn while clouds race overhead"
+)
+
+
+def main(argv: Sequence[str]) -> None:
+ if len(argv) > 1:
+ raise app.UsageError("Unexpected command line arguments.")
+
+ client = xai_sdk.Client()
+
+ winter = client.image.sample(WINTER_PROMPT, model=IMAGE_MODEL.value,
aspect_ratio="16:9", image_format="url")
+ stills = {"winter": winter.url}
+ print(f"winter: {stills['winter']}")
+ for season, prompt in SEASON_EDITS.items():
+ edited = client.image.sample(prompt, model=IMAGE_MODEL.value,
image_url=stills["winter"], image_format="url")
+ stills[season] = edited.url
+ print(f"{season}: {stills[season]}")
+
+ duration = DURATION.value
+ print(f"Rendering a {duration}s clip...")
+ response = client.video.generate(
+ prompt=VIDEO_PROMPT,
+ model=MODEL.value,
+ # Alongside keyframes or a last frame, `image_url` pins the exact
first frame.
+ image_url=stills["winter"],
+ keyframes=[
+ {"image_url": stills["spring"], "timestamp": duration / 3},
+ {"image_url": stills["summer"], "timestamp": 2 * duration / 3},
+ ],
+ last_frame_url=stills["autumn"],
+ duration=duration,
+ aspect_ratio="16:9",
+ resolution=cast(VideoResolution, RESOLUTION.value),
+ )
+
+ print(f"Video URL: {response.url}")
+ if response.cost_usd is not None:
+ print(f"Cost in USD: ${response.cost_usd:.2f}")
+
+
+if __name__ == "__main__":
+ app.run(main)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/xai_sdk-1.19.0/src/xai_sdk/__about__.py
new/xai_sdk-1.20.0/src/xai_sdk/__about__.py
--- old/xai_sdk-1.19.0/src/xai_sdk/__about__.py 2020-02-02 01:00:00.000000000
+0100
+++ new/xai_sdk-1.20.0/src/xai_sdk/__about__.py 2020-02-02 01:00:00.000000000
+0100
@@ -3,4 +3,4 @@
# To change the version, do so using `uv run hatch version <new-version>`
# or `uv run hatch version <patch|minor|major>` to bump the patch/minor/major
version respectively.
# See https://hatch.pypa.io/latest/version/#updating for more details.
-__version__ = "1.19.0"
+__version__ = "1.20.0"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/xai_sdk-1.19.0/src/xai_sdk/aio/video.py
new/xai_sdk-1.20.0/src/xai_sdk/aio/video.py
--- old/xai_sdk-1.19.0/src/xai_sdk/aio/video.py 2020-02-02 01:00:00.000000000
+0100
+++ new/xai_sdk-1.20.0/src/xai_sdk/aio/video.py 2020-02-02 01:00:00.000000000
+0100
@@ -9,7 +9,7 @@
from ..poll_timer import PollTimer
from ..proto import batch_pb2, deferred_pb2, image_pb2, video_pb2
from ..telemetry import get_tracer
-from ..types import ReferenceAudio, VideoGenerationModel
+from ..types import Keyframe, ReferenceAudio, VideoGenerationModel
from ..video import (
DEFAULT_VIDEO_POLL_INTERVAL,
DEFAULT_VIDEO_TIMEOUT,
@@ -40,6 +40,9 @@
batch_request_id: Optional[str] = None,
image_url: Optional[str] = None,
image_file_id: Optional[str] = None,
+ last_frame_url: Optional[str] = None,
+ last_frame_file_id: Optional[str] = None,
+ keyframes: Optional[Sequence[Keyframe]] = None,
video_url: Optional[str] = None,
video_file_id: Optional[str] = None,
duration: Optional[int] = None,
@@ -66,6 +69,16 @@
Cannot be set together with ``image_file_id``.
image_file_id: The file ID of an input image to use as a starting
frame.
Cannot be set together with ``image_url``.
+ last_frame_url: The URL of an image to pin as the exact last frame.
+ Cannot be set together with ``last_frame_file_id``. Only
supported
+ for ``grok-imagine-video-1.5``.
+ last_frame_file_id: The file ID of an image to pin as the exact
last frame.
+ Cannot be set together with ``last_frame_url``. Only supported
+ for ``grok-imagine-video-1.5``.
+ keyframes: Optional list of mid-video keyframes. Each entry is a
TypedDict
+ such as ``{"image_url": "https://...", "timestamp": 2.0}`` or
+ ``{"image_file_id": "file_...", "timestamp": 2.0}``. At most
four
+ entries. Only supported for ``grok-imagine-video-1.5``.
video_url: The URL of an input video to use as a starting point.
Cannot be set together with ``video_file_id``.
video_file_id: The file ID of an input video to use as a starting
point.
@@ -74,8 +87,8 @@
aspect_ratio: The aspect ratio of the video to generate.
resolution: The video resolution to generate.
reference_image_urls: Optional list of reference image URLs for
- reference-to-video (R2V) generation. When provided (and
`image_url`
- is not set), generates video using these images as
style/content references.
+ reference-to-video (R2V) generation. When provided, generates
video using
+ these images as style/content references.
May be combined with ``reference_image_file_ids`` to mix
URL/base64
and file-ID references in the same request; file IDs are
appended
first in that case.
@@ -136,6 +149,9 @@
model,
image_url=image_url,
image_file_id=image_file_id,
+ last_frame_url=last_frame_url,
+ last_frame_file_id=last_frame_file_id,
+ keyframes=keyframes,
video_url=video_url,
video_file_id=video_file_id,
duration=duration,
@@ -214,6 +230,9 @@
*,
image_url: Optional[str] = None,
image_file_id: Optional[str] = None,
+ last_frame_url: Optional[str] = None,
+ last_frame_file_id: Optional[str] = None,
+ keyframes: Optional[Sequence[Keyframe]] = None,
video_url: Optional[str] = None,
video_file_id: Optional[str] = None,
duration: Optional[int] = None,
@@ -234,6 +253,9 @@
model,
image_url=image_url,
image_file_id=image_file_id,
+ last_frame_url=last_frame_url,
+ last_frame_file_id=last_frame_file_id,
+ keyframes=keyframes,
video_url=video_url,
video_file_id=video_file_id,
duration=duration,
@@ -265,6 +287,9 @@
*,
image_url: Optional[str] = None,
image_file_id: Optional[str] = None,
+ last_frame_url: Optional[str] = None,
+ last_frame_file_id: Optional[str] = None,
+ keyframes: Optional[Sequence[Keyframe]] = None,
video_url: Optional[str] = None,
video_file_id: Optional[str] = None,
duration: Optional[int] = None,
@@ -293,14 +318,36 @@
- **Video editing**: A `video_url` or `video_file_id` is provided; the
video is edited
based on the prompt.
+ On ``grok-imagine-video-1.5``, reference-to-video can also pin exact
frames:
+ `last_frame_url` / `last_frame_file_id` pins the last frame,
`keyframes` pins
+ images at chosen timestamps inside the clip, and `image_url` /
`image_file_id`
+ then pins the first frame. The prompt may be empty when a frame is
pinned.
+
Args:
prompt: The text prompt to generate a video from.
model: The model to use for video generation.
image_url: The URL or base64-encoded data URL of an input image to
use as
the first frame (image-to-video). Cannot be combined with
`image_file_id`
- or `video_url`.
+ or `video_url`. On ``grok-imagine-video-1.5``, combining it
with reference
+ inputs, `last_frame_url`, or `keyframes` pins it as the exact
first frame.
image_file_id: The file ID of an input image to use as the first
frame
(image-to-video). Cannot be combined with `image_url`.
+ last_frame_url: The URL or base64-encoded data URL of an image to
pin as the
+ exact last frame; the video ends arriving on it. Set together
with
+ `image_url` or `image_file_id` to interpolate between a pinned
first and
+ last frame. Cannot be combined with `last_frame_file_id`. Only
supported
+ for ``grok-imagine-video-1.5``.
+ last_frame_file_id: The file ID of an image to pin as the exact
last frame.
+ Cannot be combined with `last_frame_url`. Only supported for
+ ``grok-imagine-video-1.5``.
+ keyframes: Optional list of mid-video keyframes that pin images at
chosen
+ moments inside the clip. Each entry is a TypedDict such as
+ ``{"image_url": "https://...", "timestamp": 2.0}`` or
+ ``{"image_file_id": "file_...", "timestamp": 2.0}``. Each
``timestamp`` is
+ in seconds and must fall strictly inside the clip (``0 <
timestamp < duration``),
+ and keyframes must be at least 1/3 second apart. Pin the
endpoints with
+ `image_url` / `last_frame_url` instead. At most four entries.
Only
+ supported for ``grok-imagine-video-1.5``.
video_url: The URL or base64-encoded data URL of an input video to
edit
based on the prompt (video-to-video). Cannot be combined with
`video_file_id`
or `image_url`.
@@ -313,8 +360,8 @@
resolution: The video resolution to generate.
Defaults to ``"480p"`` if not specified.
reference_image_urls: Optional list of reference image URLs for
- reference-to-video (R2V) generation. When provided (and
`image_url`
- is not set), generates video using these images as
style/content references.
+ reference-to-video (R2V) generation. When provided, generates
video using
+ these images as style/content references.
May be combined with `reference_image_file_ids` to mix
URL/base64
and file-ID references in the same request; file IDs are
appended
first in that case.
@@ -391,6 +438,28 @@
video_url="https://example.com/my-video.mp4",
)
print(response.url)
+
+ # First & last frame
+ response = await client.video.generate(
+ prompt="The camera dollies from the sunlit doorway to the
window",
+ model="grok-imagine-video-1.5",
+ image_url="https://example.com/first-frame.jpg",
+ last_frame_url="https://example.com/last-frame.jpg",
+ )
+ print(response.url)
+
+ # Keyframes
+ response = await client.video.generate(
+ prompt="The sketch on the bench becomes a clay model, then a
finished bronze",
+ model="grok-imagine-video-1.5",
+ image_url="https://example.com/sketch.jpg",
+ keyframes=[
+ {"image_url": "https://example.com/clay.jpg", "timestamp":
3.0},
+ {"image_url": "https://example.com/bronze.jpg",
"timestamp": 6.0},
+ ],
+ duration=8,
+ )
+ print(response.url)
```
"""
timer = PollTimer(
@@ -403,6 +472,9 @@
model,
image_url=image_url,
image_file_id=image_file_id,
+ last_frame_url=last_frame_url,
+ last_frame_file_id=last_frame_file_id,
+ keyframes=keyframes,
video_url=video_url,
video_file_id=video_file_id,
duration=duration,
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/xai_sdk-1.19.0/src/xai_sdk/proto/v5/video_pb2.py
new/xai_sdk-1.20.0/src/xai_sdk/proto/v5/video_pb2.py
--- old/xai_sdk-1.19.0/src/xai_sdk/proto/v5/video_pb2.py 2020-02-02
01:00:00.000000000 +0100
+++ new/xai_sdk-1.20.0/src/xai_sdk/proto/v5/video_pb2.py 2020-02-02
01:00:00.000000000 +0100
@@ -27,7 +27,7 @@
from . import usage_pb2 as xai_dot_api_dot_v1_dot_usage__pb2
-DESCRIPTOR =
_descriptor_pool.Default().AddSerializedFile(b'\n\x16xai/api/v1/video.proto\x12\x07xai_api\x1a\x19xai/api/v1/deferred.proto\x1a\x16xai/api/v1/image.proto\x1a\x16xai/api/v1/usage.proto\"J\n\x0fVideoUrlContent\x12\x12\n\x03url\x18\x01
\x01(\tH\x00R\x03url\x12\x19\n\x07\x66ile_id\x18\x02
\x01(\tH\x00R\x06\x66ileIdB\x08\n\x06source\">\n\x0f\x41udioUrlContent\x12\x1b\n\x08voice_id\x18\x02
\x01(\tH\x00R\x07voiceIdB\x08\n\x06sourceJ\x04\x08\x01\x10\x02\"\x9e\x05\n\x14GenerateVideoRequest\x12\x16\n\x06prompt\x18\x01
\x01(\tR\x06prompt\x12.\n\x05image\x18\x02
\x01(\x0b\x32\x18.xai_api.ImageUrlContentR\x05image\x12\x14\n\x05model\x18\x03
\x01(\tR\x05model\x12\x1f\n\x08\x64uration\x18\x04
\x01(\x05H\x00R\x08\x64uration\x88\x01\x01\x12.\n\x05video\x18\x06
\x01(\x0b\x32\x18.xai_api.VideoUrlContentR\x05video\x12\x41\n\x0c\x61spect_ratio\x18\x07
\x01(\x0e\x32\x19.xai_api.VideoAspectRatioH\x01R\x0b\x61spectRatio\x88\x01\x01\x12=\n\nresolution\x18\x08
\x01(\x0e\x32\x18.xai_api.VideoReso
lutionH\x02R\nresolution\x88\x01\x01\x12\x43\n\x10reference_images\x18\r
\x03(\x0b\x32\x18.xai_api.ImageUrlContentR\x0freferenceImages\x12\x45\n\x0fstorage_options\x18\x0e
\x01(\x0b\x32\x17.xai_api.StorageOptionsH\x03R\x0estorageOptions\x88\x01\x01\x12\x43\n\x10reference_audios\x18\x10
\x03(\x0b\x32\x18.xai_api.AudioUrlContentR\x0freferenceAudios\x12*\n\x0egenerate_audio\x18\x11
\x01(\x08H\x04R\rgenerateAudio\x88\x01\x01\x42\x0b\n\t_durationB\x0f\n\r_aspect_ratioB\r\n\x0b_resolutionB\x12\n\x10_storage_optionsB\x11\n\x0f_generate_audioJ\x04\x08\x0f\x10\x10\"8\n\x17GetDeferredVideoRequest\x12\x1d\n\nrequest_id\x18\x01
\x01(\tR\trequestId\"\xd8\x01\n\rVideoResponse\x12-\n\x05video\x18\x01
\x01(\x0b\x32\x17.xai_api.GeneratedVideoR\x05video\x12\x14\n\x05model\x18\x02
\x01(\tR\x05model\x12,\n\x05usage\x18\x03
\x01(\x0b\x32\x16.xai_api.SamplingUsageR\x05usage\x12.\n\x05\x65rror\x18\x06
\x01(\x0b\x32\x13.xai_api.VideoErrorH\x00R\x05\x65rror\x88\x01\x01\x12\x1a\n\x08progress\x18\x07
\x01(\x0
5R\x08progressB\x08\n\x06_error\"\xf4\x01\n\x0eGeneratedVideo\x12\x10\n\x03url\x18\x01
\x01(\tR\x03url\x12\x1a\n\x08\x64uration\x18\x04
\x01(\x05R\x08\x64uration\x12-\n\x12respect_moderation\x18\x05
\x01(\x08R\x11respectModeration\x12\x39\n\x0b\x66ile_output\x18\x06
\x01(\x0b\x32\x13.xai_api.FileOutputH\x00R\nfileOutput\x88\x01\x01\x12(\n\rstorage_error\x18\x07
\x01(\tH\x01R\x0cstorageError\x88\x01\x01\x42\x0e\n\x0c_file_outputB\x10\n\x0e_storage_error\"\x91\x01\n\x18GetDeferredVideoResponse\x12/\n\x06status\x18\x01
\x01(\x0e\x32\x17.xai_api.DeferredStatusR\x06status\x12\x37\n\x08response\x18\x02
\x01(\x0b\x32\x16.xai_api.VideoResponseH\x00R\x08response\x88\x01\x01\x42\x0b\n\t_response\":\n\nVideoError\x12\x12\n\x04\x63ode\x18\x01
\x01(\tR\x04\x63ode\x12\x18\n\x07message\x18\x02
\x01(\tR\x07message\"\x81\x02\n\x12\x45xtendVideoRequest\x12\x16\n\x06prompt\x18\x01
\x01(\tR\x06prompt\x12.\n\x05video\x18\x02
\x01(\x0b\x32\x18.xai_api.VideoUrlContentR\x05video\x12\x14\n\x05model\x18\x03
\x01(\tR\x05model\x12\x1f\n\x08\x64uration\x18\x04
\x01(\x05H\x00R\x08\x64uration\x88\x01\x01\x12\x45\n\x0fstorage_options\x18\x06
\x01(\x0b\x32\x17.xai_api.StorageOptionsH\x01R\x0estorageOptions\x88\x01\x01\x42\x0b\n\t_durationB\x12\n\x10_storage_optionsJ\x04\x08\x07\x10\x08*\xfc\x01\n\x10VideoAspectRatio\x12\"\n\x1eVIDEO_ASPECT_RATIO_UNSPECIFIED\x10\x00\x12\x1a\n\x16VIDEO_ASPECT_RATIO_1_1\x10\x01\x12\x1b\n\x17VIDEO_ASPECT_RATIO_16_9\x10\x02\x12\x1b\n\x17VIDEO_ASPECT_RATIO_9_16\x10\x03\x12\x1a\n\x16VIDEO_ASPECT_RATIO_4_3\x10\x04\x12\x1a\n\x16VIDEO_ASPECT_RATIO_3_4\x10\x05\x12\x1a\n\x16VIDEO_ASPECT_RATIO_3_2\x10\x06\x12\x1a\n\x16VIDEO_ASPECT_RATIO_2_3\x10\x07*\x85\x01\n\x0fVideoResolution\x12
\n\x1cVIDEO_RESOLUTION_UNSPECIFIED\x10\x00\x12\x19\n\x15VIDEO_RESOLUTION_480P\x10\x01\x12\x19\n\x15VIDEO_RESOLUTION_720P\x10\x02\x12\x1a\n\x16VIDEO_RESOLUTION_1080P\x10\x03\x32\x82\x02\n\x05Video\x12P\n\rGenerateVideo\x12\x1d.xai_api.GenerateVideoRequest\x1a\x1e.xai_api.StartDeferredResponse\"\
x00\x12L\n\x0b\x45xtendVideo\x12\x1b.xai_api.ExtendVideoRequest\x1a\x1e.xai_api.StartDeferredResponse\"\x00\x12Y\n\x10GetDeferredVideo\x12
.xai_api.GetDeferredVideoRequest\x1a!.xai_api.GetDeferredVideoResponse\"\x00\x42Q\n\x0b\x63om.xai_apiB\nVideoProtoP\x01\xa2\x02\x03XXX\xaa\x02\x06XaiApi\xca\x02\x06XaiApi\xe2\x02\x12XaiApi\\GPBMetadata\xea\x02\x06XaiApib\x06proto3')
+DESCRIPTOR =
_descriptor_pool.Default().AddSerializedFile(b'\n\x16xai/api/v1/video.proto\x12\x07xai_api\x1a\x19xai/api/v1/deferred.proto\x1a\x16xai/api/v1/image.proto\x1a\x16xai/api/v1/usage.proto\"J\n\x0fVideoUrlContent\x12\x12\n\x03url\x18\x01
\x01(\tH\x00R\x03url\x12\x19\n\x07\x66ile_id\x18\x02
\x01(\tH\x00R\x06\x66ileIdB\x08\n\x06source\">\n\x0f\x41udioUrlContent\x12\x1b\n\x08voice_id\x18\x02
\x01(\tH\x00R\x07voiceIdB\x08\n\x06sourceJ\x04\x08\x01\x10\x02\"`\n\rVideoKeyframe\x12.\n\x05image\x18\x01
\x01(\x0b\x32\x18.xai_api.ImageUrlContentR\x05image\x12\x1f\n\x0btimestamp_s\x18\x02
\x01(\x02R\ntimestampS\"\x8d\x06\n\x14GenerateVideoRequest\x12\x16\n\x06prompt\x18\x01
\x01(\tR\x06prompt\x12.\n\x05image\x18\x02
\x01(\x0b\x32\x18.xai_api.ImageUrlContentR\x05image\x12\x14\n\x05model\x18\x03
\x01(\tR\x05model\x12\x1f\n\x08\x64uration\x18\x04
\x01(\x05H\x00R\x08\x64uration\x88\x01\x01\x12.\n\x05video\x18\x06
\x01(\x0b\x32\x18.xai_api.VideoUrlContentR\x05video\x12\x41\n\x0c\x61spect_rat
io\x18\x07
\x01(\x0e\x32\x19.xai_api.VideoAspectRatioH\x01R\x0b\x61spectRatio\x88\x01\x01\x12=\n\nresolution\x18\x08
\x01(\x0e\x32\x18.xai_api.VideoResolutionH\x02R\nresolution\x88\x01\x01\x12\x43\n\x10reference_images\x18\r
\x03(\x0b\x32\x18.xai_api.ImageUrlContentR\x0freferenceImages\x12\x45\n\x0fstorage_options\x18\x0e
\x01(\x0b\x32\x17.xai_api.StorageOptionsH\x03R\x0estorageOptions\x88\x01\x01\x12\x43\n\x10reference_audios\x18\x10
\x03(\x0b\x32\x18.xai_api.AudioUrlContentR\x0freferenceAudios\x12*\n\x0egenerate_audio\x18\x11
\x01(\x08H\x04R\rgenerateAudio\x88\x01\x01\x12\x37\n\nlast_frame\x18\x12
\x01(\x0b\x32\x18.xai_api.ImageUrlContentR\tlastFrame\x12\x34\n\tkeyframes\x18\x14
\x03(\x0b\x32\x16.xai_api.VideoKeyframeR\tkeyframesB\x0b\n\t_durationB\x0f\n\r_aspect_ratioB\r\n\x0b_resolutionB\x12\n\x10_storage_optionsB\x11\n\x0f_generate_audioJ\x04\x08\x0f\x10\x10\"8\n\x17GetDeferredVideoRequest\x12\x1d\n\nrequest_id\x18\x01
\x01(\tR\trequestId\"\xd8\x01\n\rVideoResponse\x12-\n\x05vi
deo\x18\x01
\x01(\x0b\x32\x17.xai_api.GeneratedVideoR\x05video\x12\x14\n\x05model\x18\x02
\x01(\tR\x05model\x12,\n\x05usage\x18\x03
\x01(\x0b\x32\x16.xai_api.SamplingUsageR\x05usage\x12.\n\x05\x65rror\x18\x06
\x01(\x0b\x32\x13.xai_api.VideoErrorH\x00R\x05\x65rror\x88\x01\x01\x12\x1a\n\x08progress\x18\x07
\x01(\x05R\x08progressB\x08\n\x06_error\"\xf4\x01\n\x0eGeneratedVideo\x12\x10\n\x03url\x18\x01
\x01(\tR\x03url\x12\x1a\n\x08\x64uration\x18\x04
\x01(\x05R\x08\x64uration\x12-\n\x12respect_moderation\x18\x05
\x01(\x08R\x11respectModeration\x12\x39\n\x0b\x66ile_output\x18\x06
\x01(\x0b\x32\x13.xai_api.FileOutputH\x00R\nfileOutput\x88\x01\x01\x12(\n\rstorage_error\x18\x07
\x01(\tH\x01R\x0cstorageError\x88\x01\x01\x42\x0e\n\x0c_file_outputB\x10\n\x0e_storage_error\"\x91\x01\n\x18GetDeferredVideoResponse\x12/\n\x06status\x18\x01
\x01(\x0e\x32\x17.xai_api.DeferredStatusR\x06status\x12\x37\n\x08response\x18\x02
\x01(\x0b\x32\x16.xai_api.VideoResponseH\x00R\x08response\x88\x01\x01\x42\x0b\n
\t_response\":\n\nVideoError\x12\x12\n\x04\x63ode\x18\x01
\x01(\tR\x04\x63ode\x12\x18\n\x07message\x18\x02
\x01(\tR\x07message\"\x81\x02\n\x12\x45xtendVideoRequest\x12\x16\n\x06prompt\x18\x01
\x01(\tR\x06prompt\x12.\n\x05video\x18\x02
\x01(\x0b\x32\x18.xai_api.VideoUrlContentR\x05video\x12\x14\n\x05model\x18\x03
\x01(\tR\x05model\x12\x1f\n\x08\x64uration\x18\x04
\x01(\x05H\x00R\x08\x64uration\x88\x01\x01\x12\x45\n\x0fstorage_options\x18\x06
\x01(\x0b\x32\x17.xai_api.StorageOptionsH\x01R\x0estorageOptions\x88\x01\x01\x42\x0b\n\t_durationB\x12\n\x10_storage_optionsJ\x04\x08\x07\x10\x08*\xfc\x01\n\x10VideoAspectRatio\x12\"\n\x1eVIDEO_ASPECT_RATIO_UNSPECIFIED\x10\x00\x12\x1a\n\x16VIDEO_ASPECT_RATIO_1_1\x10\x01\x12\x1b\n\x17VIDEO_ASPECT_RATIO_16_9\x10\x02\x12\x1b\n\x17VIDEO_ASPECT_RATIO_9_16\x10\x03\x12\x1a\n\x16VIDEO_ASPECT_RATIO_4_3\x10\x04\x12\x1a\n\x16VIDEO_ASPECT_RATIO_3_4\x10\x05\x12\x1a\n\x16VIDEO_ASPECT_RATIO_3_2\x10\x06\x12\x1a\n\x16VIDEO_ASPECT_RATIO_2_3\x10\x07*\x85\x01\n\x0fV
ideoResolution\x12
\n\x1cVIDEO_RESOLUTION_UNSPECIFIED\x10\x00\x12\x19\n\x15VIDEO_RESOLUTION_480P\x10\x01\x12\x19\n\x15VIDEO_RESOLUTION_720P\x10\x02\x12\x1a\n\x16VIDEO_RESOLUTION_1080P\x10\x03\x32\x82\x02\n\x05Video\x12P\n\rGenerateVideo\x12\x1d.xai_api.GenerateVideoRequest\x1a\x1e.xai_api.StartDeferredResponse\"\x00\x12L\n\x0b\x45xtendVideo\x12\x1b.xai_api.ExtendVideoRequest\x1a\x1e.xai_api.StartDeferredResponse\"\x00\x12Y\n\x10GetDeferredVideo\x12
.xai_api.GetDeferredVideoRequest\x1a!.xai_api.GetDeferredVideoResponse\"\x00\x42Q\n\x0b\x63om.xai_apiB\nVideoProtoP\x01\xa2\x02\x03XXX\xaa\x02\x06XaiApi\xca\x02\x06XaiApi\xe2\x02\x12XaiApi\\GPBMetadata\xea\x02\x06XaiApib\x06proto3')
_globals = globals()
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
@@ -35,28 +35,30 @@
if not _descriptor._USE_C_DESCRIPTORS:
_globals['DESCRIPTOR']._loaded_options = None
_globals['DESCRIPTOR']._serialized_options =
b'\n\013com.xai_apiB\nVideoProtoP\001\242\002\003XXX\252\002\006XaiApi\312\002\006XaiApi\342\002\022XaiApi\\GPBMetadata\352\002\006XaiApi'
- _globals['_VIDEOASPECTRATIO']._serialized_start=1916
- _globals['_VIDEOASPECTRATIO']._serialized_end=2168
- _globals['_VIDEORESOLUTION']._serialized_start=2171
- _globals['_VIDEORESOLUTION']._serialized_end=2304
+ _globals['_VIDEOASPECTRATIO']._serialized_start=2125
+ _globals['_VIDEOASPECTRATIO']._serialized_end=2377
+ _globals['_VIDEORESOLUTION']._serialized_start=2380
+ _globals['_VIDEORESOLUTION']._serialized_end=2513
_globals['_VIDEOURLCONTENT']._serialized_start=110
_globals['_VIDEOURLCONTENT']._serialized_end=184
_globals['_AUDIOURLCONTENT']._serialized_start=186
_globals['_AUDIOURLCONTENT']._serialized_end=248
- _globals['_GENERATEVIDEOREQUEST']._serialized_start=251
- _globals['_GENERATEVIDEOREQUEST']._serialized_end=921
- _globals['_GETDEFERREDVIDEOREQUEST']._serialized_start=923
- _globals['_GETDEFERREDVIDEOREQUEST']._serialized_end=979
- _globals['_VIDEORESPONSE']._serialized_start=982
- _globals['_VIDEORESPONSE']._serialized_end=1198
- _globals['_GENERATEDVIDEO']._serialized_start=1201
- _globals['_GENERATEDVIDEO']._serialized_end=1445
- _globals['_GETDEFERREDVIDEORESPONSE']._serialized_start=1448
- _globals['_GETDEFERREDVIDEORESPONSE']._serialized_end=1593
- _globals['_VIDEOERROR']._serialized_start=1595
- _globals['_VIDEOERROR']._serialized_end=1653
- _globals['_EXTENDVIDEOREQUEST']._serialized_start=1656
- _globals['_EXTENDVIDEOREQUEST']._serialized_end=1913
- _globals['_VIDEO']._serialized_start=2307
- _globals['_VIDEO']._serialized_end=2565
+ _globals['_VIDEOKEYFRAME']._serialized_start=250
+ _globals['_VIDEOKEYFRAME']._serialized_end=346
+ _globals['_GENERATEVIDEOREQUEST']._serialized_start=349
+ _globals['_GENERATEVIDEOREQUEST']._serialized_end=1130
+ _globals['_GETDEFERREDVIDEOREQUEST']._serialized_start=1132
+ _globals['_GETDEFERREDVIDEOREQUEST']._serialized_end=1188
+ _globals['_VIDEORESPONSE']._serialized_start=1191
+ _globals['_VIDEORESPONSE']._serialized_end=1407
+ _globals['_GENERATEDVIDEO']._serialized_start=1410
+ _globals['_GENERATEDVIDEO']._serialized_end=1654
+ _globals['_GETDEFERREDVIDEORESPONSE']._serialized_start=1657
+ _globals['_GETDEFERREDVIDEORESPONSE']._serialized_end=1802
+ _globals['_VIDEOERROR']._serialized_start=1804
+ _globals['_VIDEOERROR']._serialized_end=1862
+ _globals['_EXTENDVIDEOREQUEST']._serialized_start=1865
+ _globals['_EXTENDVIDEOREQUEST']._serialized_end=2122
+ _globals['_VIDEO']._serialized_start=2516
+ _globals['_VIDEO']._serialized_end=2774
# @@protoc_insertion_point(module_scope)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/xai_sdk-1.19.0/src/xai_sdk/proto/v5/video_pb2.pyi
new/xai_sdk-1.20.0/src/xai_sdk/proto/v5/video_pb2.pyi
--- old/xai_sdk-1.19.0/src/xai_sdk/proto/v5/video_pb2.pyi 2020-02-02
01:00:00.000000000 +0100
+++ new/xai_sdk-1.20.0/src/xai_sdk/proto/v5/video_pb2.pyi 2020-02-02
01:00:00.000000000 +0100
@@ -53,8 +53,16 @@
voice_id: str
def __init__(self, voice_id: _Optional[str] = ...) -> None: ...
+class VideoKeyframe(_message.Message):
+ __slots__ = ("image", "timestamp_s")
+ IMAGE_FIELD_NUMBER: _ClassVar[int]
+ TIMESTAMP_S_FIELD_NUMBER: _ClassVar[int]
+ image: _image_pb2.ImageUrlContent
+ timestamp_s: float
+ def __init__(self, image: _Optional[_Union[_image_pb2.ImageUrlContent,
_Mapping]] = ..., timestamp_s: _Optional[float] = ...) -> None: ...
+
class GenerateVideoRequest(_message.Message):
- __slots__ = ("prompt", "image", "model", "duration", "video",
"aspect_ratio", "resolution", "reference_images", "storage_options",
"reference_audios", "generate_audio")
+ __slots__ = ("prompt", "image", "model", "duration", "video",
"aspect_ratio", "resolution", "reference_images", "storage_options",
"reference_audios", "generate_audio", "last_frame", "keyframes")
PROMPT_FIELD_NUMBER: _ClassVar[int]
IMAGE_FIELD_NUMBER: _ClassVar[int]
MODEL_FIELD_NUMBER: _ClassVar[int]
@@ -66,6 +74,8 @@
STORAGE_OPTIONS_FIELD_NUMBER: _ClassVar[int]
REFERENCE_AUDIOS_FIELD_NUMBER: _ClassVar[int]
GENERATE_AUDIO_FIELD_NUMBER: _ClassVar[int]
+ LAST_FRAME_FIELD_NUMBER: _ClassVar[int]
+ KEYFRAMES_FIELD_NUMBER: _ClassVar[int]
prompt: str
image: _image_pb2.ImageUrlContent
model: str
@@ -77,7 +87,9 @@
storage_options: _image_pb2.StorageOptions
reference_audios:
_containers.RepeatedCompositeFieldContainer[AudioUrlContent]
generate_audio: bool
- def __init__(self, prompt: _Optional[str] = ..., image:
_Optional[_Union[_image_pb2.ImageUrlContent, _Mapping]] = ..., model:
_Optional[str] = ..., duration: _Optional[int] = ..., video:
_Optional[_Union[VideoUrlContent, _Mapping]] = ..., aspect_ratio:
_Optional[_Union[VideoAspectRatio, str]] = ..., resolution:
_Optional[_Union[VideoResolution, str]] = ..., reference_images:
_Optional[_Iterable[_Union[_image_pb2.ImageUrlContent, _Mapping]]] = ...,
storage_options: _Optional[_Union[_image_pb2.StorageOptions, _Mapping]] = ...,
reference_audios: _Optional[_Iterable[_Union[AudioUrlContent, _Mapping]]] =
..., generate_audio: bool = ...) -> None: ...
+ last_frame: _image_pb2.ImageUrlContent
+ keyframes: _containers.RepeatedCompositeFieldContainer[VideoKeyframe]
+ def __init__(self, prompt: _Optional[str] = ..., image:
_Optional[_Union[_image_pb2.ImageUrlContent, _Mapping]] = ..., model:
_Optional[str] = ..., duration: _Optional[int] = ..., video:
_Optional[_Union[VideoUrlContent, _Mapping]] = ..., aspect_ratio:
_Optional[_Union[VideoAspectRatio, str]] = ..., resolution:
_Optional[_Union[VideoResolution, str]] = ..., reference_images:
_Optional[_Iterable[_Union[_image_pb2.ImageUrlContent, _Mapping]]] = ...,
storage_options: _Optional[_Union[_image_pb2.StorageOptions, _Mapping]] = ...,
reference_audios: _Optional[_Iterable[_Union[AudioUrlContent, _Mapping]]] =
..., generate_audio: bool = ..., last_frame:
_Optional[_Union[_image_pb2.ImageUrlContent, _Mapping]] = ..., keyframes:
_Optional[_Iterable[_Union[VideoKeyframe, _Mapping]]] = ...) -> None: ...
class GetDeferredVideoRequest(_message.Message):
__slots__ = ("request_id",)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/xai_sdk-1.19.0/src/xai_sdk/proto/v6/video_pb2.py
new/xai_sdk-1.20.0/src/xai_sdk/proto/v6/video_pb2.py
--- old/xai_sdk-1.19.0/src/xai_sdk/proto/v6/video_pb2.py 2020-02-02
01:00:00.000000000 +0100
+++ new/xai_sdk-1.20.0/src/xai_sdk/proto/v6/video_pb2.py 2020-02-02
01:00:00.000000000 +0100
@@ -27,7 +27,7 @@
from . import usage_pb2 as xai_dot_api_dot_v1_dot_usage__pb2
-DESCRIPTOR =
_descriptor_pool.Default().AddSerializedFile(b'\n\x16xai/api/v1/video.proto\x12\x07xai_api\x1a\x19xai/api/v1/deferred.proto\x1a\x16xai/api/v1/image.proto\x1a\x16xai/api/v1/usage.proto\"J\n\x0fVideoUrlContent\x12\x12\n\x03url\x18\x01
\x01(\tH\x00R\x03url\x12\x19\n\x07\x66ile_id\x18\x02
\x01(\tH\x00R\x06\x66ileIdB\x08\n\x06source\">\n\x0f\x41udioUrlContent\x12\x1b\n\x08voice_id\x18\x02
\x01(\tH\x00R\x07voiceIdB\x08\n\x06sourceJ\x04\x08\x01\x10\x02\"\x9e\x05\n\x14GenerateVideoRequest\x12\x16\n\x06prompt\x18\x01
\x01(\tR\x06prompt\x12.\n\x05image\x18\x02
\x01(\x0b\x32\x18.xai_api.ImageUrlContentR\x05image\x12\x14\n\x05model\x18\x03
\x01(\tR\x05model\x12\x1f\n\x08\x64uration\x18\x04
\x01(\x05H\x00R\x08\x64uration\x88\x01\x01\x12.\n\x05video\x18\x06
\x01(\x0b\x32\x18.xai_api.VideoUrlContentR\x05video\x12\x41\n\x0c\x61spect_ratio\x18\x07
\x01(\x0e\x32\x19.xai_api.VideoAspectRatioH\x01R\x0b\x61spectRatio\x88\x01\x01\x12=\n\nresolution\x18\x08
\x01(\x0e\x32\x18.xai_api.VideoReso
lutionH\x02R\nresolution\x88\x01\x01\x12\x43\n\x10reference_images\x18\r
\x03(\x0b\x32\x18.xai_api.ImageUrlContentR\x0freferenceImages\x12\x45\n\x0fstorage_options\x18\x0e
\x01(\x0b\x32\x17.xai_api.StorageOptionsH\x03R\x0estorageOptions\x88\x01\x01\x12\x43\n\x10reference_audios\x18\x10
\x03(\x0b\x32\x18.xai_api.AudioUrlContentR\x0freferenceAudios\x12*\n\x0egenerate_audio\x18\x11
\x01(\x08H\x04R\rgenerateAudio\x88\x01\x01\x42\x0b\n\t_durationB\x0f\n\r_aspect_ratioB\r\n\x0b_resolutionB\x12\n\x10_storage_optionsB\x11\n\x0f_generate_audioJ\x04\x08\x0f\x10\x10\"8\n\x17GetDeferredVideoRequest\x12\x1d\n\nrequest_id\x18\x01
\x01(\tR\trequestId\"\xd8\x01\n\rVideoResponse\x12-\n\x05video\x18\x01
\x01(\x0b\x32\x17.xai_api.GeneratedVideoR\x05video\x12\x14\n\x05model\x18\x02
\x01(\tR\x05model\x12,\n\x05usage\x18\x03
\x01(\x0b\x32\x16.xai_api.SamplingUsageR\x05usage\x12.\n\x05\x65rror\x18\x06
\x01(\x0b\x32\x13.xai_api.VideoErrorH\x00R\x05\x65rror\x88\x01\x01\x12\x1a\n\x08progress\x18\x07
\x01(\x0
5R\x08progressB\x08\n\x06_error\"\xf4\x01\n\x0eGeneratedVideo\x12\x10\n\x03url\x18\x01
\x01(\tR\x03url\x12\x1a\n\x08\x64uration\x18\x04
\x01(\x05R\x08\x64uration\x12-\n\x12respect_moderation\x18\x05
\x01(\x08R\x11respectModeration\x12\x39\n\x0b\x66ile_output\x18\x06
\x01(\x0b\x32\x13.xai_api.FileOutputH\x00R\nfileOutput\x88\x01\x01\x12(\n\rstorage_error\x18\x07
\x01(\tH\x01R\x0cstorageError\x88\x01\x01\x42\x0e\n\x0c_file_outputB\x10\n\x0e_storage_error\"\x91\x01\n\x18GetDeferredVideoResponse\x12/\n\x06status\x18\x01
\x01(\x0e\x32\x17.xai_api.DeferredStatusR\x06status\x12\x37\n\x08response\x18\x02
\x01(\x0b\x32\x16.xai_api.VideoResponseH\x00R\x08response\x88\x01\x01\x42\x0b\n\t_response\":\n\nVideoError\x12\x12\n\x04\x63ode\x18\x01
\x01(\tR\x04\x63ode\x12\x18\n\x07message\x18\x02
\x01(\tR\x07message\"\x81\x02\n\x12\x45xtendVideoRequest\x12\x16\n\x06prompt\x18\x01
\x01(\tR\x06prompt\x12.\n\x05video\x18\x02
\x01(\x0b\x32\x18.xai_api.VideoUrlContentR\x05video\x12\x14\n\x05model\x18\x03
\x01(\tR\x05model\x12\x1f\n\x08\x64uration\x18\x04
\x01(\x05H\x00R\x08\x64uration\x88\x01\x01\x12\x45\n\x0fstorage_options\x18\x06
\x01(\x0b\x32\x17.xai_api.StorageOptionsH\x01R\x0estorageOptions\x88\x01\x01\x42\x0b\n\t_durationB\x12\n\x10_storage_optionsJ\x04\x08\x07\x10\x08*\xfc\x01\n\x10VideoAspectRatio\x12\"\n\x1eVIDEO_ASPECT_RATIO_UNSPECIFIED\x10\x00\x12\x1a\n\x16VIDEO_ASPECT_RATIO_1_1\x10\x01\x12\x1b\n\x17VIDEO_ASPECT_RATIO_16_9\x10\x02\x12\x1b\n\x17VIDEO_ASPECT_RATIO_9_16\x10\x03\x12\x1a\n\x16VIDEO_ASPECT_RATIO_4_3\x10\x04\x12\x1a\n\x16VIDEO_ASPECT_RATIO_3_4\x10\x05\x12\x1a\n\x16VIDEO_ASPECT_RATIO_3_2\x10\x06\x12\x1a\n\x16VIDEO_ASPECT_RATIO_2_3\x10\x07*\x85\x01\n\x0fVideoResolution\x12
\n\x1cVIDEO_RESOLUTION_UNSPECIFIED\x10\x00\x12\x19\n\x15VIDEO_RESOLUTION_480P\x10\x01\x12\x19\n\x15VIDEO_RESOLUTION_720P\x10\x02\x12\x1a\n\x16VIDEO_RESOLUTION_1080P\x10\x03\x32\x82\x02\n\x05Video\x12P\n\rGenerateVideo\x12\x1d.xai_api.GenerateVideoRequest\x1a\x1e.xai_api.StartDeferredResponse\"\
x00\x12L\n\x0b\x45xtendVideo\x12\x1b.xai_api.ExtendVideoRequest\x1a\x1e.xai_api.StartDeferredResponse\"\x00\x12Y\n\x10GetDeferredVideo\x12
.xai_api.GetDeferredVideoRequest\x1a!.xai_api.GetDeferredVideoResponse\"\x00\x42Q\n\x0b\x63om.xai_apiB\nVideoProtoP\x01\xa2\x02\x03XXX\xaa\x02\x06XaiApi\xca\x02\x06XaiApi\xe2\x02\x12XaiApi\\GPBMetadata\xea\x02\x06XaiApib\x06proto3')
+DESCRIPTOR =
_descriptor_pool.Default().AddSerializedFile(b'\n\x16xai/api/v1/video.proto\x12\x07xai_api\x1a\x19xai/api/v1/deferred.proto\x1a\x16xai/api/v1/image.proto\x1a\x16xai/api/v1/usage.proto\"J\n\x0fVideoUrlContent\x12\x12\n\x03url\x18\x01
\x01(\tH\x00R\x03url\x12\x19\n\x07\x66ile_id\x18\x02
\x01(\tH\x00R\x06\x66ileIdB\x08\n\x06source\">\n\x0f\x41udioUrlContent\x12\x1b\n\x08voice_id\x18\x02
\x01(\tH\x00R\x07voiceIdB\x08\n\x06sourceJ\x04\x08\x01\x10\x02\"`\n\rVideoKeyframe\x12.\n\x05image\x18\x01
\x01(\x0b\x32\x18.xai_api.ImageUrlContentR\x05image\x12\x1f\n\x0btimestamp_s\x18\x02
\x01(\x02R\ntimestampS\"\x8d\x06\n\x14GenerateVideoRequest\x12\x16\n\x06prompt\x18\x01
\x01(\tR\x06prompt\x12.\n\x05image\x18\x02
\x01(\x0b\x32\x18.xai_api.ImageUrlContentR\x05image\x12\x14\n\x05model\x18\x03
\x01(\tR\x05model\x12\x1f\n\x08\x64uration\x18\x04
\x01(\x05H\x00R\x08\x64uration\x88\x01\x01\x12.\n\x05video\x18\x06
\x01(\x0b\x32\x18.xai_api.VideoUrlContentR\x05video\x12\x41\n\x0c\x61spect_rat
io\x18\x07
\x01(\x0e\x32\x19.xai_api.VideoAspectRatioH\x01R\x0b\x61spectRatio\x88\x01\x01\x12=\n\nresolution\x18\x08
\x01(\x0e\x32\x18.xai_api.VideoResolutionH\x02R\nresolution\x88\x01\x01\x12\x43\n\x10reference_images\x18\r
\x03(\x0b\x32\x18.xai_api.ImageUrlContentR\x0freferenceImages\x12\x45\n\x0fstorage_options\x18\x0e
\x01(\x0b\x32\x17.xai_api.StorageOptionsH\x03R\x0estorageOptions\x88\x01\x01\x12\x43\n\x10reference_audios\x18\x10
\x03(\x0b\x32\x18.xai_api.AudioUrlContentR\x0freferenceAudios\x12*\n\x0egenerate_audio\x18\x11
\x01(\x08H\x04R\rgenerateAudio\x88\x01\x01\x12\x37\n\nlast_frame\x18\x12
\x01(\x0b\x32\x18.xai_api.ImageUrlContentR\tlastFrame\x12\x34\n\tkeyframes\x18\x14
\x03(\x0b\x32\x16.xai_api.VideoKeyframeR\tkeyframesB\x0b\n\t_durationB\x0f\n\r_aspect_ratioB\r\n\x0b_resolutionB\x12\n\x10_storage_optionsB\x11\n\x0f_generate_audioJ\x04\x08\x0f\x10\x10\"8\n\x17GetDeferredVideoRequest\x12\x1d\n\nrequest_id\x18\x01
\x01(\tR\trequestId\"\xd8\x01\n\rVideoResponse\x12-\n\x05vi
deo\x18\x01
\x01(\x0b\x32\x17.xai_api.GeneratedVideoR\x05video\x12\x14\n\x05model\x18\x02
\x01(\tR\x05model\x12,\n\x05usage\x18\x03
\x01(\x0b\x32\x16.xai_api.SamplingUsageR\x05usage\x12.\n\x05\x65rror\x18\x06
\x01(\x0b\x32\x13.xai_api.VideoErrorH\x00R\x05\x65rror\x88\x01\x01\x12\x1a\n\x08progress\x18\x07
\x01(\x05R\x08progressB\x08\n\x06_error\"\xf4\x01\n\x0eGeneratedVideo\x12\x10\n\x03url\x18\x01
\x01(\tR\x03url\x12\x1a\n\x08\x64uration\x18\x04
\x01(\x05R\x08\x64uration\x12-\n\x12respect_moderation\x18\x05
\x01(\x08R\x11respectModeration\x12\x39\n\x0b\x66ile_output\x18\x06
\x01(\x0b\x32\x13.xai_api.FileOutputH\x00R\nfileOutput\x88\x01\x01\x12(\n\rstorage_error\x18\x07
\x01(\tH\x01R\x0cstorageError\x88\x01\x01\x42\x0e\n\x0c_file_outputB\x10\n\x0e_storage_error\"\x91\x01\n\x18GetDeferredVideoResponse\x12/\n\x06status\x18\x01
\x01(\x0e\x32\x17.xai_api.DeferredStatusR\x06status\x12\x37\n\x08response\x18\x02
\x01(\x0b\x32\x16.xai_api.VideoResponseH\x00R\x08response\x88\x01\x01\x42\x0b\n
\t_response\":\n\nVideoError\x12\x12\n\x04\x63ode\x18\x01
\x01(\tR\x04\x63ode\x12\x18\n\x07message\x18\x02
\x01(\tR\x07message\"\x81\x02\n\x12\x45xtendVideoRequest\x12\x16\n\x06prompt\x18\x01
\x01(\tR\x06prompt\x12.\n\x05video\x18\x02
\x01(\x0b\x32\x18.xai_api.VideoUrlContentR\x05video\x12\x14\n\x05model\x18\x03
\x01(\tR\x05model\x12\x1f\n\x08\x64uration\x18\x04
\x01(\x05H\x00R\x08\x64uration\x88\x01\x01\x12\x45\n\x0fstorage_options\x18\x06
\x01(\x0b\x32\x17.xai_api.StorageOptionsH\x01R\x0estorageOptions\x88\x01\x01\x42\x0b\n\t_durationB\x12\n\x10_storage_optionsJ\x04\x08\x07\x10\x08*\xfc\x01\n\x10VideoAspectRatio\x12\"\n\x1eVIDEO_ASPECT_RATIO_UNSPECIFIED\x10\x00\x12\x1a\n\x16VIDEO_ASPECT_RATIO_1_1\x10\x01\x12\x1b\n\x17VIDEO_ASPECT_RATIO_16_9\x10\x02\x12\x1b\n\x17VIDEO_ASPECT_RATIO_9_16\x10\x03\x12\x1a\n\x16VIDEO_ASPECT_RATIO_4_3\x10\x04\x12\x1a\n\x16VIDEO_ASPECT_RATIO_3_4\x10\x05\x12\x1a\n\x16VIDEO_ASPECT_RATIO_3_2\x10\x06\x12\x1a\n\x16VIDEO_ASPECT_RATIO_2_3\x10\x07*\x85\x01\n\x0fV
ideoResolution\x12
\n\x1cVIDEO_RESOLUTION_UNSPECIFIED\x10\x00\x12\x19\n\x15VIDEO_RESOLUTION_480P\x10\x01\x12\x19\n\x15VIDEO_RESOLUTION_720P\x10\x02\x12\x1a\n\x16VIDEO_RESOLUTION_1080P\x10\x03\x32\x82\x02\n\x05Video\x12P\n\rGenerateVideo\x12\x1d.xai_api.GenerateVideoRequest\x1a\x1e.xai_api.StartDeferredResponse\"\x00\x12L\n\x0b\x45xtendVideo\x12\x1b.xai_api.ExtendVideoRequest\x1a\x1e.xai_api.StartDeferredResponse\"\x00\x12Y\n\x10GetDeferredVideo\x12
.xai_api.GetDeferredVideoRequest\x1a!.xai_api.GetDeferredVideoResponse\"\x00\x42Q\n\x0b\x63om.xai_apiB\nVideoProtoP\x01\xa2\x02\x03XXX\xaa\x02\x06XaiApi\xca\x02\x06XaiApi\xe2\x02\x12XaiApi\\GPBMetadata\xea\x02\x06XaiApib\x06proto3')
_globals = globals()
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
@@ -35,28 +35,30 @@
if not _descriptor._USE_C_DESCRIPTORS:
_globals['DESCRIPTOR']._loaded_options = None
_globals['DESCRIPTOR']._serialized_options =
b'\n\013com.xai_apiB\nVideoProtoP\001\242\002\003XXX\252\002\006XaiApi\312\002\006XaiApi\342\002\022XaiApi\\GPBMetadata\352\002\006XaiApi'
- _globals['_VIDEOASPECTRATIO']._serialized_start=1916
- _globals['_VIDEOASPECTRATIO']._serialized_end=2168
- _globals['_VIDEORESOLUTION']._serialized_start=2171
- _globals['_VIDEORESOLUTION']._serialized_end=2304
+ _globals['_VIDEOASPECTRATIO']._serialized_start=2125
+ _globals['_VIDEOASPECTRATIO']._serialized_end=2377
+ _globals['_VIDEORESOLUTION']._serialized_start=2380
+ _globals['_VIDEORESOLUTION']._serialized_end=2513
_globals['_VIDEOURLCONTENT']._serialized_start=110
_globals['_VIDEOURLCONTENT']._serialized_end=184
_globals['_AUDIOURLCONTENT']._serialized_start=186
_globals['_AUDIOURLCONTENT']._serialized_end=248
- _globals['_GENERATEVIDEOREQUEST']._serialized_start=251
- _globals['_GENERATEVIDEOREQUEST']._serialized_end=921
- _globals['_GETDEFERREDVIDEOREQUEST']._serialized_start=923
- _globals['_GETDEFERREDVIDEOREQUEST']._serialized_end=979
- _globals['_VIDEORESPONSE']._serialized_start=982
- _globals['_VIDEORESPONSE']._serialized_end=1198
- _globals['_GENERATEDVIDEO']._serialized_start=1201
- _globals['_GENERATEDVIDEO']._serialized_end=1445
- _globals['_GETDEFERREDVIDEORESPONSE']._serialized_start=1448
- _globals['_GETDEFERREDVIDEORESPONSE']._serialized_end=1593
- _globals['_VIDEOERROR']._serialized_start=1595
- _globals['_VIDEOERROR']._serialized_end=1653
- _globals['_EXTENDVIDEOREQUEST']._serialized_start=1656
- _globals['_EXTENDVIDEOREQUEST']._serialized_end=1913
- _globals['_VIDEO']._serialized_start=2307
- _globals['_VIDEO']._serialized_end=2565
+ _globals['_VIDEOKEYFRAME']._serialized_start=250
+ _globals['_VIDEOKEYFRAME']._serialized_end=346
+ _globals['_GENERATEVIDEOREQUEST']._serialized_start=349
+ _globals['_GENERATEVIDEOREQUEST']._serialized_end=1130
+ _globals['_GETDEFERREDVIDEOREQUEST']._serialized_start=1132
+ _globals['_GETDEFERREDVIDEOREQUEST']._serialized_end=1188
+ _globals['_VIDEORESPONSE']._serialized_start=1191
+ _globals['_VIDEORESPONSE']._serialized_end=1407
+ _globals['_GENERATEDVIDEO']._serialized_start=1410
+ _globals['_GENERATEDVIDEO']._serialized_end=1654
+ _globals['_GETDEFERREDVIDEORESPONSE']._serialized_start=1657
+ _globals['_GETDEFERREDVIDEORESPONSE']._serialized_end=1802
+ _globals['_VIDEOERROR']._serialized_start=1804
+ _globals['_VIDEOERROR']._serialized_end=1862
+ _globals['_EXTENDVIDEOREQUEST']._serialized_start=1865
+ _globals['_EXTENDVIDEOREQUEST']._serialized_end=2122
+ _globals['_VIDEO']._serialized_start=2516
+ _globals['_VIDEO']._serialized_end=2774
# @@protoc_insertion_point(module_scope)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/xai_sdk-1.19.0/src/xai_sdk/proto/v6/video_pb2.pyi
new/xai_sdk-1.20.0/src/xai_sdk/proto/v6/video_pb2.pyi
--- old/xai_sdk-1.19.0/src/xai_sdk/proto/v6/video_pb2.pyi 2020-02-02
01:00:00.000000000 +0100
+++ new/xai_sdk-1.20.0/src/xai_sdk/proto/v6/video_pb2.pyi 2020-02-02
01:00:00.000000000 +0100
@@ -54,8 +54,16 @@
voice_id: str
def __init__(self, voice_id: _Optional[str] = ...) -> None: ...
+class VideoKeyframe(_message.Message):
+ __slots__ = ("image", "timestamp_s")
+ IMAGE_FIELD_NUMBER: _ClassVar[int]
+ TIMESTAMP_S_FIELD_NUMBER: _ClassVar[int]
+ image: _image_pb2.ImageUrlContent
+ timestamp_s: float
+ def __init__(self, image: _Optional[_Union[_image_pb2.ImageUrlContent,
_Mapping]] = ..., timestamp_s: _Optional[float] = ...) -> None: ...
+
class GenerateVideoRequest(_message.Message):
- __slots__ = ("prompt", "image", "model", "duration", "video",
"aspect_ratio", "resolution", "reference_images", "storage_options",
"reference_audios", "generate_audio")
+ __slots__ = ("prompt", "image", "model", "duration", "video",
"aspect_ratio", "resolution", "reference_images", "storage_options",
"reference_audios", "generate_audio", "last_frame", "keyframes")
PROMPT_FIELD_NUMBER: _ClassVar[int]
IMAGE_FIELD_NUMBER: _ClassVar[int]
MODEL_FIELD_NUMBER: _ClassVar[int]
@@ -67,6 +75,8 @@
STORAGE_OPTIONS_FIELD_NUMBER: _ClassVar[int]
REFERENCE_AUDIOS_FIELD_NUMBER: _ClassVar[int]
GENERATE_AUDIO_FIELD_NUMBER: _ClassVar[int]
+ LAST_FRAME_FIELD_NUMBER: _ClassVar[int]
+ KEYFRAMES_FIELD_NUMBER: _ClassVar[int]
prompt: str
image: _image_pb2.ImageUrlContent
model: str
@@ -78,7 +88,9 @@
storage_options: _image_pb2.StorageOptions
reference_audios:
_containers.RepeatedCompositeFieldContainer[AudioUrlContent]
generate_audio: bool
- def __init__(self, prompt: _Optional[str] = ..., image:
_Optional[_Union[_image_pb2.ImageUrlContent, _Mapping]] = ..., model:
_Optional[str] = ..., duration: _Optional[int] = ..., video:
_Optional[_Union[VideoUrlContent, _Mapping]] = ..., aspect_ratio:
_Optional[_Union[VideoAspectRatio, str]] = ..., resolution:
_Optional[_Union[VideoResolution, str]] = ..., reference_images:
_Optional[_Iterable[_Union[_image_pb2.ImageUrlContent, _Mapping]]] = ...,
storage_options: _Optional[_Union[_image_pb2.StorageOptions, _Mapping]] = ...,
reference_audios: _Optional[_Iterable[_Union[AudioUrlContent, _Mapping]]] =
..., generate_audio: bool = ...) -> None: ...
+ last_frame: _image_pb2.ImageUrlContent
+ keyframes: _containers.RepeatedCompositeFieldContainer[VideoKeyframe]
+ def __init__(self, prompt: _Optional[str] = ..., image:
_Optional[_Union[_image_pb2.ImageUrlContent, _Mapping]] = ..., model:
_Optional[str] = ..., duration: _Optional[int] = ..., video:
_Optional[_Union[VideoUrlContent, _Mapping]] = ..., aspect_ratio:
_Optional[_Union[VideoAspectRatio, str]] = ..., resolution:
_Optional[_Union[VideoResolution, str]] = ..., reference_images:
_Optional[_Iterable[_Union[_image_pb2.ImageUrlContent, _Mapping]]] = ...,
storage_options: _Optional[_Union[_image_pb2.StorageOptions, _Mapping]] = ...,
reference_audios: _Optional[_Iterable[_Union[AudioUrlContent, _Mapping]]] =
..., generate_audio: bool = ..., last_frame:
_Optional[_Union[_image_pb2.ImageUrlContent, _Mapping]] = ..., keyframes:
_Optional[_Iterable[_Union[VideoKeyframe, _Mapping]]] = ...) -> None: ...
class GetDeferredVideoRequest(_message.Message):
__slots__ = ("request_id",)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/xai_sdk-1.19.0/src/xai_sdk/sync/video.py
new/xai_sdk-1.20.0/src/xai_sdk/sync/video.py
--- old/xai_sdk-1.19.0/src/xai_sdk/sync/video.py 2020-02-02
01:00:00.000000000 +0100
+++ new/xai_sdk-1.20.0/src/xai_sdk/sync/video.py 2020-02-02
01:00:00.000000000 +0100
@@ -9,7 +9,7 @@
from ..poll_timer import PollTimer
from ..proto import batch_pb2, deferred_pb2, image_pb2, video_pb2
from ..telemetry import get_tracer
-from ..types import ReferenceAudio, VideoGenerationModel
+from ..types import Keyframe, ReferenceAudio, VideoGenerationModel
from ..video import (
DEFAULT_VIDEO_POLL_INTERVAL,
DEFAULT_VIDEO_TIMEOUT,
@@ -40,6 +40,9 @@
batch_request_id: Optional[str] = None,
image_url: Optional[str] = None,
image_file_id: Optional[str] = None,
+ last_frame_url: Optional[str] = None,
+ last_frame_file_id: Optional[str] = None,
+ keyframes: Optional[Sequence[Keyframe]] = None,
video_url: Optional[str] = None,
video_file_id: Optional[str] = None,
duration: Optional[int] = None,
@@ -66,6 +69,16 @@
Cannot be set together with ``image_file_id``.
image_file_id: The file ID of an input image to use as a starting
frame.
Cannot be set together with ``image_url``.
+ last_frame_url: The URL of an image to pin as the exact last frame.
+ Cannot be set together with ``last_frame_file_id``. Only
supported
+ for ``grok-imagine-video-1.5``.
+ last_frame_file_id: The file ID of an image to pin as the exact
last frame.
+ Cannot be set together with ``last_frame_url``. Only supported
+ for ``grok-imagine-video-1.5``.
+ keyframes: Optional list of mid-video keyframes. Each entry is a
TypedDict
+ such as ``{"image_url": "https://...", "timestamp": 2.0}`` or
+ ``{"image_file_id": "file_...", "timestamp": 2.0}``. At most
four
+ entries. Only supported for ``grok-imagine-video-1.5``.
video_url: The URL of an input video to use as a starting point.
Cannot be set together with ``video_file_id``.
video_file_id: The file ID of an input video to use as a starting
point.
@@ -74,8 +87,8 @@
aspect_ratio: The aspect ratio of the video to generate.
resolution: The video resolution to generate.
reference_image_urls: Optional list of reference image URLs for
- reference-to-video (R2V) generation. When provided (and
`image_url`
- is not set), generates video using these images as
style/content references.
+ reference-to-video (R2V) generation. When provided, generates
video using
+ these images as style/content references.
May be combined with ``reference_image_file_ids`` to mix
URL/base64
and file-ID references in the same request; file IDs are
appended
first in that case.
@@ -136,6 +149,9 @@
model,
image_url=image_url,
image_file_id=image_file_id,
+ last_frame_url=last_frame_url,
+ last_frame_file_id=last_frame_file_id,
+ keyframes=keyframes,
video_url=video_url,
video_file_id=video_file_id,
duration=duration,
@@ -214,6 +230,9 @@
*,
image_url: Optional[str] = None,
image_file_id: Optional[str] = None,
+ last_frame_url: Optional[str] = None,
+ last_frame_file_id: Optional[str] = None,
+ keyframes: Optional[Sequence[Keyframe]] = None,
video_url: Optional[str] = None,
video_file_id: Optional[str] = None,
duration: Optional[int] = None,
@@ -234,6 +253,9 @@
model,
image_url=image_url,
image_file_id=image_file_id,
+ last_frame_url=last_frame_url,
+ last_frame_file_id=last_frame_file_id,
+ keyframes=keyframes,
video_url=video_url,
video_file_id=video_file_id,
duration=duration,
@@ -265,6 +287,9 @@
*,
image_url: Optional[str] = None,
image_file_id: Optional[str] = None,
+ last_frame_url: Optional[str] = None,
+ last_frame_file_id: Optional[str] = None,
+ keyframes: Optional[Sequence[Keyframe]] = None,
video_url: Optional[str] = None,
video_file_id: Optional[str] = None,
duration: Optional[int] = None,
@@ -293,14 +318,36 @@
- **Video editing**: A `video_url` or `video_file_id` is provided; the
video is edited
based on the prompt.
+ On ``grok-imagine-video-1.5``, reference-to-video can also pin exact
frames:
+ `last_frame_url` / `last_frame_file_id` pins the last frame,
`keyframes` pins
+ images at chosen timestamps inside the clip, and `image_url` /
`image_file_id`
+ then pins the first frame. The prompt may be empty when a frame is
pinned.
+
Args:
prompt: The text prompt to generate a video from.
model: The model to use for video generation.
image_url: The URL or base64-encoded data URL of an input image to
use as
the first frame (image-to-video). Cannot be combined with
`image_file_id`
- or `video_url`.
+ or `video_url`. On ``grok-imagine-video-1.5``, combining it
with reference
+ inputs, `last_frame_url`, or `keyframes` pins it as the exact
first frame.
image_file_id: The file ID of an input image to use as the first
frame
(image-to-video). Cannot be combined with `image_url`.
+ last_frame_url: The URL or base64-encoded data URL of an image to
pin as the
+ exact last frame; the video ends arriving on it. Set together
with
+ `image_url` or `image_file_id` to interpolate between a pinned
first and
+ last frame. Cannot be combined with `last_frame_file_id`. Only
supported
+ for ``grok-imagine-video-1.5``.
+ last_frame_file_id: The file ID of an image to pin as the exact
last frame.
+ Cannot be combined with `last_frame_url`. Only supported for
+ ``grok-imagine-video-1.5``.
+ keyframes: Optional list of mid-video keyframes that pin images at
chosen
+ moments inside the clip. Each entry is a TypedDict such as
+ ``{"image_url": "https://...", "timestamp": 2.0}`` or
+ ``{"image_file_id": "file_...", "timestamp": 2.0}``. Each
``timestamp`` is
+ in seconds and must fall strictly inside the clip (``0 <
timestamp < duration``),
+ and keyframes must be at least 1/3 second apart. Pin the
endpoints with
+ `image_url` / `last_frame_url` instead. At most four entries.
Only
+ supported for ``grok-imagine-video-1.5``.
video_url: The URL or base64-encoded data URL of an input video to
edit
based on the prompt (video-to-video). Cannot be combined with
`video_file_id`
or `image_url`.
@@ -313,8 +360,8 @@
resolution: The video resolution to generate.
Defaults to ``"480p"`` if not specified.
reference_image_urls: Optional list of reference image URLs for
- reference-to-video (R2V) generation. When provided (and
`image_url`
- is not set), generates video using these images as
style/content references.
+ reference-to-video (R2V) generation. When provided, generates
video using
+ these images as style/content references.
May be combined with `reference_image_file_ids` to mix
URL/base64
and file-ID references in the same request; file IDs are
appended
first in that case.
@@ -391,6 +438,28 @@
video_url="https://example.com/my-video.mp4",
)
print(response.url)
+
+ # First & last frame
+ response = client.video.generate(
+ prompt="The camera dollies from the sunlit doorway to the
window",
+ model="grok-imagine-video-1.5",
+ image_url="https://example.com/first-frame.jpg",
+ last_frame_url="https://example.com/last-frame.jpg",
+ )
+ print(response.url)
+
+ # Keyframes
+ response = client.video.generate(
+ prompt="The sketch on the bench becomes a clay model, then a
finished bronze",
+ model="grok-imagine-video-1.5",
+ image_url="https://example.com/sketch.jpg",
+ keyframes=[
+ {"image_url": "https://example.com/clay.jpg", "timestamp":
3.0},
+ {"image_url": "https://example.com/bronze.jpg",
"timestamp": 6.0},
+ ],
+ duration=8,
+ )
+ print(response.url)
```
"""
timer = PollTimer(
@@ -403,6 +472,9 @@
model,
image_url=image_url,
image_file_id=image_file_id,
+ last_frame_url=last_frame_url,
+ last_frame_file_id=last_frame_file_id,
+ keyframes=keyframes,
video_url=video_url,
video_file_id=video_file_id,
duration=duration,
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/xai_sdk-1.19.0/src/xai_sdk/types/__init__.py
new/xai_sdk-1.20.0/src/xai_sdk/types/__init__.py
--- old/xai_sdk-1.19.0/src/xai_sdk/types/__init__.py 2020-02-02
01:00:00.000000000 +0100
+++ new/xai_sdk-1.20.0/src/xai_sdk/types/__init__.py 2020-02-02
01:00:00.000000000 +0100
@@ -12,7 +12,17 @@
from .common import ServiceTier
from .image import ImageAspectRatio, ImageFormat, ImageQuality, ImageResolution
from .model import AllModels, ChatModel, ImageGenerationModel,
VideoGenerationModel
-from .video import ReferenceAudio, ReferenceAudioValidator, VideoAspectRatio,
VideoResolution, VoiceAudioRef
+from .video import (
+ FileIdKeyframe,
+ Keyframe,
+ KeyframeValidator,
+ ReferenceAudio,
+ ReferenceAudioValidator,
+ UrlKeyframe,
+ VideoAspectRatio,
+ VideoResolution,
+ VoiceAudioRef,
+)
__all__ = [
"AgentCount",
@@ -20,6 +30,7 @@
"AllModels",
"ChatModel",
"Content",
+ "FileIdKeyframe",
"ImageAspectRatio",
"ImageDetail",
"ImageFormat",
@@ -28,12 +39,15 @@
"ImageResolution",
"IncludeOption",
"IncludeOptionMap",
+ "Keyframe",
+ "KeyframeValidator",
"ReasoningEffort",
"ReferenceAudio",
"ReferenceAudioValidator",
"ResponseFormat",
"ServiceTier",
"ToolMode",
+ "UrlKeyframe",
"VideoAspectRatio",
"VideoGenerationModel",
"VideoResolution",
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/xai_sdk-1.19.0/src/xai_sdk/types/video.py
new/xai_sdk-1.20.0/src/xai_sdk/types/video.py
--- old/xai_sdk-1.19.0/src/xai_sdk/types/video.py 2020-02-02
01:00:00.000000000 +0100
+++ new/xai_sdk-1.20.0/src/xai_sdk/types/video.py 2020-02-02
01:00:00.000000000 +0100
@@ -1,13 +1,17 @@
-from typing import Annotated, Literal, TypeAlias
+from typing import Annotated, Literal, TypeAlias, Union
-from pydantic import StringConstraints, TypeAdapter
+from pydantic import ConfigDict, StringConstraints, TypeAdapter
from typing_extensions import TypedDict
from ..proto import video_pb2
__all__ = [
+ "FileIdKeyframe",
+ "Keyframe",
+ "KeyframeValidator",
"ReferenceAudio",
"ReferenceAudioValidator",
+ "UrlKeyframe",
"VideoAspectRatio",
"VideoAspectRatioMap",
"VideoResolution",
@@ -53,6 +57,36 @@
ReferenceAudioValidator = TypeAdapter(ReferenceAudio)
+class UrlKeyframe(TypedDict):
+ """Mid-video keyframe whose image is given as a URL or base64-encoded data
URL.
+
+ The video passes through ``image_url`` exactly ``timestamp`` seconds into
the
+ clip, which must fall strictly inside it (``0 < timestamp < duration``).
+ """
+
+ image_url: _NonEmptyStr
+ timestamp: float
+
+
+class FileIdKeyframe(TypedDict):
+ """Mid-video keyframe whose image is a file ID from the Files API.
+
+ The video passes through the image exactly ``timestamp`` seconds into the
+ clip, which must fall strictly inside it (``0 < timestamp < duration``).
+ """
+
+ image_file_id: _NonEmptyStr
+ timestamp: float
+
+
+# Structured keyframe entry for ``keyframes``: exactly one image source plus a
timestamp.
+Keyframe: TypeAlias = Union[UrlKeyframe, FileIdKeyframe]
+
+# Runtime validation for public ``keyframes`` entries. Forbidding extra keys
rejects
+# entries that set both ``image_url`` and ``image_file_id``.
+KeyframeValidator = TypeAdapter(Keyframe, config=ConfigDict(extra="forbid"))
+
+
VideoAspectRatioMap: dict[VideoAspectRatio, "video_pb2.VideoAspectRatio"] = {
"1:1": video_pb2.VideoAspectRatio.VIDEO_ASPECT_RATIO_1_1,
"16:9": video_pb2.VideoAspectRatio.VIDEO_ASPECT_RATIO_16_9,
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/xai_sdk-1.19.0/src/xai_sdk/video.py
new/xai_sdk-1.20.0/src/xai_sdk/video.py
--- old/xai_sdk-1.19.0/src/xai_sdk/video.py 2020-02-02 01:00:00.000000000
+0100
+++ new/xai_sdk-1.20.0/src/xai_sdk/video.py 2020-02-02 01:00:00.000000000
+0100
@@ -10,6 +10,8 @@
from .telemetry import should_disable_sensitive_attributes
from .types import VideoGenerationModel
from .types.video import (
+ Keyframe,
+ KeyframeValidator,
ReferenceAudio,
ReferenceAudioValidator,
VideoAspectRatio,
@@ -132,12 +134,16 @@
image_file_id: Optional[str] = None,
video_url: Optional[str] = None,
video_file_id: Optional[str] = None,
+ last_frame_url: Optional[str] = None,
+ last_frame_file_id: Optional[str] = None,
) -> None:
"""Validates mutual exclusion constraints on video input parameters."""
if image_url is not None and image_file_id is not None:
raise ValueError("Only one of image_url or image_file_id can be set
for a request.")
if video_url is not None and video_file_id is not None:
raise ValueError("Only one of video_url or video_file_id can be set
for a request.")
+ if last_frame_url is not None and last_frame_file_id is not None:
+ raise ValueError("Only one of last_frame_url or last_frame_file_id can
be set for a request.")
def _make_generate_request(
@@ -146,6 +152,9 @@
*,
image_url: Optional[str],
image_file_id: Optional[str] = None,
+ last_frame_url: Optional[str] = None,
+ last_frame_file_id: Optional[str] = None,
+ keyframes: Optional[Sequence[Keyframe]] = None,
video_url: Optional[str],
video_file_id: Optional[str] = None,
duration: Optional[int],
@@ -158,7 +167,12 @@
storage_options: Optional[Union[StorageOptions, image_pb2.StorageOptions]]
= None,
) -> video_pb2.GenerateVideoRequest:
_validate_video_inputs(
- image_url=image_url, image_file_id=image_file_id, video_url=video_url,
video_file_id=video_file_id
+ image_url=image_url,
+ image_file_id=image_file_id,
+ video_url=video_url,
+ video_file_id=video_file_id,
+ last_frame_url=last_frame_url,
+ last_frame_file_id=last_frame_file_id,
)
request = video_pb2.GenerateVideoRequest(prompt=prompt, model=model)
@@ -169,6 +183,8 @@
request.image.CopyFrom(
image_pb2.ImageUrlContent(file_id=image_file_id,
detail=image_pb2.ImageDetail.DETAIL_AUTO)
)
+ _set_last_frame(request, last_frame_url, last_frame_file_id)
+ _set_keyframes(request, keyframes)
if video_url is not None:
request.video.CopyFrom(video_pb2.VideoUrlContent(url=video_url))
elif video_file_id is not None:
@@ -242,6 +258,44 @@
request.reference_audios.extend(_resolve_reference_audio(entry) for
entry in reference_audios)
+def _set_last_frame(
+ request: video_pb2.GenerateVideoRequest,
+ url: Optional[str],
+ file_id: Optional[str],
+) -> None:
+ """Populates `last_frame` from a URL/base64 string or a file ID."""
+ if url is not None:
+ request.last_frame.CopyFrom(image_pb2.ImageUrlContent(image_url=url,
detail=image_pb2.ImageDetail.DETAIL_AUTO))
+ elif file_id is not None:
+ request.last_frame.CopyFrom(
+ image_pb2.ImageUrlContent(file_id=file_id,
detail=image_pb2.ImageDetail.DETAIL_AUTO)
+ )
+
+
+def _resolve_keyframe(entry: Keyframe) -> video_pb2.VideoKeyframe:
+ """Converts a public ``keyframes`` entry into the request wire form.
+
+ Validates with :data:`~xai_sdk.types.video.KeyframeValidator` (Pydantic).
+ Timestamp bounds and grid spacing depend on the requested duration and are
+ enforced server-side.
+ """
+ validated = KeyframeValidator.validate_python(entry)
+ if "image_url" in validated:
+ image = image_pb2.ImageUrlContent(image_url=validated["image_url"],
detail=image_pb2.ImageDetail.DETAIL_AUTO)
+ else:
+ image = image_pb2.ImageUrlContent(file_id=validated["image_file_id"],
detail=image_pb2.ImageDetail.DETAIL_AUTO)
+ return video_pb2.VideoKeyframe(image=image,
timestamp_s=validated["timestamp"])
+
+
+def _set_keyframes(
+ request: video_pb2.GenerateVideoRequest,
+ keyframes: Optional[Sequence[Keyframe]],
+) -> None:
+ """Populates `keyframes` from validated TypedDict entries."""
+ if keyframes is not None:
+ request.keyframes.extend(_resolve_keyframe(entry) for entry in
keyframes)
+
+
def _make_span_request_attributes(request: video_pb2.GenerateVideoRequest) ->
dict[str, Any]:
"""Creates the video generation span request attributes."""
attributes: dict[str, Any] = {
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/xai_sdk-1.19.0/tests/aio/video_test.py
new/xai_sdk-1.20.0/tests/aio/video_test.py
--- old/xai_sdk-1.19.0/tests/aio/video_test.py 2020-02-02 01:00:00.000000000
+0100
+++ new/xai_sdk-1.20.0/tests/aio/video_test.py 2020-02-02 01:00:00.000000000
+0100
@@ -250,6 +250,141 @@
assert len(batch_req.video_request.reference_audios) == 0
[email protected](loop_scope="session")
+async def test_generate_passes_first_and_last_frame(client: AsyncClient):
+ server.clear_last_video_request()
+
+ await client.video.generate(
+ prompt="",
+ model="grok-imagine-video-1.5",
+ image_url="https://example.com/first.jpg",
+ last_frame_url="https://example.com/last.jpg",
+ )
+
+ request = server.get_last_video_request()
+ assert request is not None
+ assert request.image == image_pb2.ImageUrlContent(
+ image_url="https://example.com/first.jpg",
detail=image_pb2.ImageDetail.DETAIL_AUTO
+ )
+ assert request.last_frame == image_pb2.ImageUrlContent(
+ image_url="https://example.com/last.jpg",
detail=image_pb2.ImageDetail.DETAIL_AUTO
+ )
+
+
[email protected](loop_scope="session")
+async def test_generate_passes_last_frame_file_id(client: AsyncClient):
+ server.clear_last_video_request()
+
+ await client.video.generate(prompt="foo", model="grok-imagine-video-1.5",
last_frame_file_id="file_last")
+
+ request = server.get_last_video_request()
+ assert request is not None
+ assert not request.HasField("image")
+ assert request.last_frame == image_pb2.ImageUrlContent(
+ file_id="file_last", detail=image_pb2.ImageDetail.DETAIL_AUTO
+ )
+
+
[email protected](loop_scope="session")
+async def test_generate_rejects_last_frame_url_and_file_id(client:
AsyncClient):
+ with pytest.raises(ValueError, match="Only one of last_frame_url or
last_frame_file_id can be set"):
+ await client.video.generate(
+ prompt="foo",
+ model="grok-imagine-video-1.5",
+ last_frame_url="https://example.com/last.jpg",
+ last_frame_file_id="file_last",
+ )
+
+
[email protected](loop_scope="session")
+async def test_generate_passes_keyframes(client: AsyncClient):
+ server.clear_last_video_request()
+
+ await client.video.generate(
+ prompt="foo",
+ model="grok-imagine-video-1.5",
+ duration=8,
+ keyframes=[
+ {"image_url": "https://example.com/kf1.jpg", "timestamp": 2.5},
+ {"image_file_id": "file_kf2", "timestamp": 5},
+ ],
+ )
+
+ request = server.get_last_video_request()
+ assert request is not None
+ assert list(request.keyframes) == [
+ video_pb2.VideoKeyframe(
+ image=image_pb2.ImageUrlContent(
+ image_url="https://example.com/kf1.jpg",
detail=image_pb2.ImageDetail.DETAIL_AUTO
+ ),
+ timestamp_s=2.5,
+ ),
+ video_pb2.VideoKeyframe(
+ image=image_pb2.ImageUrlContent(file_id="file_kf2",
detail=image_pb2.ImageDetail.DETAIL_AUTO),
+ timestamp_s=5.0,
+ ),
+ ]
+
+
[email protected](loop_scope="session")
[email protected](
+ "entry",
+ [
+ pytest.param(
+ {"image_url": "https://example.com/a.jpg", "image_file_id":
"file_a", "timestamp": 1.0}, id="both-sources"
+ ),
+ pytest.param({"timestamp": 1.0}, id="missing-source"),
+ pytest.param({"image_url": "https://example.com/a.jpg"},
id="missing-timestamp"),
+ pytest.param({"image_url": " ", "timestamp": 1.0}, id="blank-url"),
+ pytest.param({"image_url": "https://example.com/a.jpg", "timestamp":
"soon"}, id="wrong-timestamp-type"),
+ pytest.param({"image_url": "https://example.com/a.jpg", "timestamp":
1.0, "detail": "high"}, id="unknown-key"),
+ pytest.param({"image_url": "https://example.com/a.jpg", "timestamp_s":
1.0}, id="rest-timestamp-key"),
+ ],
+)
+async def test_generate_rejects_invalid_keyframe(client: AsyncClient, entry):
+ with pytest.raises(pydantic.ValidationError):
+ await client.video.generate(
+ prompt="foo",
+ model="grok-imagine-video-1.5",
+ keyframes=[entry], # type: ignore[list-item]
+ )
+
+
[email protected](loop_scope="session")
+async def test_generate_omits_last_frame_and_keyframes_by_default(client:
AsyncClient):
+ server.clear_last_video_request()
+
+ await client.video.generate(prompt="foo", model="grok-imagine-video")
+
+ request = server.get_last_video_request()
+ assert request is not None
+ assert not request.HasField("last_frame")
+ assert len(request.keyframes) == 0
+
+
[email protected](loop_scope="session")
+async def test_prepare_passes_last_frame_and_keyframes(client: AsyncClient):
+ batch_req = client.video.prepare(
+ prompt="foo",
+ model="grok-imagine-video-1.5",
+ image_file_id="file_first",
+ last_frame_url="https://example.com/last.jpg",
+ keyframes=[{"image_url": "https://example.com/kf.jpg", "timestamp":
3.0}],
+ )
+
+ request = batch_req.video_request
+ assert request.image.file_id == "file_first"
+ assert request.last_frame.image_url == "https://example.com/last.jpg"
+ assert list(request.keyframes) == [
+ video_pb2.VideoKeyframe(
+ image=image_pb2.ImageUrlContent(
+ image_url="https://example.com/kf.jpg",
detail=image_pb2.ImageDetail.DETAIL_AUTO
+ ),
+ timestamp_s=3.0,
+ ),
+ ]
+
+
def test_generate_audio_included_in_span_request_attributes():
request = _make_generate_request(
prompt="foo",
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/xai_sdk-1.19.0/tests/sync/video_test.py
new/xai_sdk-1.20.0/tests/sync/video_test.py
--- old/xai_sdk-1.19.0/tests/sync/video_test.py 2020-02-02 01:00:00.000000000
+0100
+++ new/xai_sdk-1.20.0/tests/sync/video_test.py 2020-02-02 01:00:00.000000000
+0100
@@ -233,6 +233,134 @@
assert len(batch_req.video_request.reference_audios) == 0
+def test_generate_passes_first_and_last_frame(client: Client):
+ server.clear_last_video_request()
+
+ client.video.generate(
+ prompt="",
+ model="grok-imagine-video-1.5",
+ image_url="https://example.com/first.jpg",
+ last_frame_url="https://example.com/last.jpg",
+ )
+
+ request = server.get_last_video_request()
+ assert request is not None
+ assert request.image == image_pb2.ImageUrlContent(
+ image_url="https://example.com/first.jpg",
detail=image_pb2.ImageDetail.DETAIL_AUTO
+ )
+ assert request.last_frame == image_pb2.ImageUrlContent(
+ image_url="https://example.com/last.jpg",
detail=image_pb2.ImageDetail.DETAIL_AUTO
+ )
+
+
+def test_generate_passes_last_frame_file_id(client: Client):
+ server.clear_last_video_request()
+
+ client.video.generate(prompt="foo", model="grok-imagine-video-1.5",
last_frame_file_id="file_last")
+
+ request = server.get_last_video_request()
+ assert request is not None
+ assert not request.HasField("image")
+ assert request.last_frame == image_pb2.ImageUrlContent(
+ file_id="file_last", detail=image_pb2.ImageDetail.DETAIL_AUTO
+ )
+
+
+def test_generate_rejects_last_frame_url_and_file_id(client: Client):
+ with pytest.raises(ValueError, match="Only one of last_frame_url or
last_frame_file_id can be set"):
+ client.video.generate(
+ prompt="foo",
+ model="grok-imagine-video-1.5",
+ last_frame_url="https://example.com/last.jpg",
+ last_frame_file_id="file_last",
+ )
+
+
+def test_generate_passes_keyframes(client: Client):
+ server.clear_last_video_request()
+
+ client.video.generate(
+ prompt="foo",
+ model="grok-imagine-video-1.5",
+ duration=8,
+ keyframes=[
+ {"image_url": "https://example.com/kf1.jpg", "timestamp": 2.5},
+ {"image_file_id": "file_kf2", "timestamp": 5},
+ ],
+ )
+
+ request = server.get_last_video_request()
+ assert request is not None
+ assert list(request.keyframes) == [
+ video_pb2.VideoKeyframe(
+ image=image_pb2.ImageUrlContent(
+ image_url="https://example.com/kf1.jpg",
detail=image_pb2.ImageDetail.DETAIL_AUTO
+ ),
+ timestamp_s=2.5,
+ ),
+ video_pb2.VideoKeyframe(
+ image=image_pb2.ImageUrlContent(file_id="file_kf2",
detail=image_pb2.ImageDetail.DETAIL_AUTO),
+ timestamp_s=5.0,
+ ),
+ ]
+
+
[email protected](
+ "entry",
+ [
+ pytest.param(
+ {"image_url": "https://example.com/a.jpg", "image_file_id":
"file_a", "timestamp": 1.0}, id="both-sources"
+ ),
+ pytest.param({"timestamp": 1.0}, id="missing-source"),
+ pytest.param({"image_url": "https://example.com/a.jpg"},
id="missing-timestamp"),
+ pytest.param({"image_url": " ", "timestamp": 1.0}, id="blank-url"),
+ pytest.param({"image_url": "https://example.com/a.jpg", "timestamp":
"soon"}, id="wrong-timestamp-type"),
+ pytest.param({"image_url": "https://example.com/a.jpg", "timestamp":
1.0, "detail": "high"}, id="unknown-key"),
+ pytest.param({"image_url": "https://example.com/a.jpg", "timestamp_s":
1.0}, id="rest-timestamp-key"),
+ ],
+)
+def test_generate_rejects_invalid_keyframe(client: Client, entry):
+ with pytest.raises(pydantic.ValidationError):
+ client.video.generate(
+ prompt="foo",
+ model="grok-imagine-video-1.5",
+ keyframes=[entry], # type: ignore[list-item]
+ )
+
+
+def test_generate_omits_last_frame_and_keyframes_by_default(client: Client):
+ server.clear_last_video_request()
+
+ client.video.generate(prompt="foo", model="grok-imagine-video")
+
+ request = server.get_last_video_request()
+ assert request is not None
+ assert not request.HasField("last_frame")
+ assert len(request.keyframes) == 0
+
+
+def test_prepare_passes_last_frame_and_keyframes(client: Client):
+ batch_req = client.video.prepare(
+ prompt="foo",
+ model="grok-imagine-video-1.5",
+ image_file_id="file_first",
+ last_frame_url="https://example.com/last.jpg",
+ keyframes=[{"image_url": "https://example.com/kf.jpg", "timestamp":
3.0}],
+ )
+
+ request = batch_req.video_request
+ assert request.image.file_id == "file_first"
+ assert request.last_frame.image_url == "https://example.com/last.jpg"
+ assert list(request.keyframes) == [
+ video_pb2.VideoKeyframe(
+ image=image_pb2.ImageUrlContent(
+ image_url="https://example.com/kf.jpg",
detail=image_pb2.ImageDetail.DETAIL_AUTO
+ ),
+ timestamp_s=3.0,
+ ),
+ ]
+
+
def test_generate_audio_included_in_span_request_attributes():
request = _make_generate_request(
prompt="foo",