Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 35 additions & 8 deletions docs/my-website/docs/providers/gemini/videos.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ LiteLLM supports Google's Veo video generation models through a unified API inte
|-------|-------|
| Description | Google's Veo AI video generation models |
| Provider Route on LiteLLM | `gemini/` |
| Supported Models | `veo-3.0-generate-preview`, `veo-3.1-generate-preview` |
| Cost Tracking | ✅ Duration-based pricing |
| Supported Models | Veo 3.0 / 3.1 preview and production IDs (see table below), including **Veo 3.1 Lite** |
| Cost Tracking | ✅ Duration-based pricing; optional **per-resolution** tiers where the catalog lists them (e.g. 720p vs 1080p) |
| Logging Support | ✅ Full request/response logging |
| Proxy Server Support | ✅ Full proxy integration with virtual keys |
| Spend Management | ✅ Budget tracking and rate limiting |
Expand Down Expand Up @@ -79,6 +79,11 @@ print("Video downloaded successfully!")
|------------|-------------|--------------|--------|
| veo-3.0-generate-preview | Veo 3.0 video generation | 8 seconds | Preview |
| veo-3.1-generate-preview | Veo 3.1 video generation | 8 seconds | Preview |
| veo-3.1-lite-generate-preview | Veo 3.1 **Lite** (cost-efficient; [Gemini pricing](https://ai.google.dev/gemini-api/docs/video)) | Per Google docs | Preview |
| veo-3.1-fast-generate-preview / `…-001` | Faster / prod variants | Per Google docs | Preview / GA |
| veo-3.1-generate-001 | Veo 3.1 production | Per Google docs | GA |

Use the full LiteLLM model id with the `gemini/` prefix (for example `gemini/veo-3.1-lite-generate-preview`).

## Video Generation Parameters

Expand All @@ -87,14 +92,29 @@ LiteLLM automatically maps OpenAI-style parameters to Veo's format:
| OpenAI Parameter | Veo Parameter | Description | Example |
|------------------|---------------|-------------|---------|
| `prompt` | `prompt` | Text description of the video | "A cat playing" |
| `size` | `aspectRatio` | Video dimensions → aspect ratio | "1280x720" → "16:9" |
| `size` | `aspectRatio` and, when applicable, **`resolution`** | Standard widths/heights map to landscape/portrait **and** to `720p` or `1080p` for the API | See below |
| `seconds` | `durationSeconds` | Duration in seconds | "8" → 8 |
| `input_reference` | `image` | Reference image to animate | File object or path |
| `model` | `model` | Model to use | "gemini/veo-3.0-generate-preview" |

### Size to Aspect Ratio Mapping
### `size` and output resolution

When you pass a **standard `size`** string, LiteLLM sets both:

- **Aspect ratio** (`16:9` or `9:16`) — same as before.
- **Output resolution** (`720p` or `1080p`) when the height is clear from the preset, so the correct Veo tier is requested without extra fields.

| `size` | Aspect ratio | Resolution sent to Veo |
|--------|----------------|-------------------------|
| `1280x720`, `720x1280` | `16:9` / `9:16` | `720p` |
| `1920x1080`, `1080x1920` | `16:9` / `9:16` | `1080p` |

Other `size` values still map to an aspect ratio (defaulting to `16:9` when unknown); resolution is left to **Google’s default** unless you set it yourself.

You can also pass Veo’s **`resolution`** (for example via `extra_body`) if you need an explicit value that does not match the presets above. If you set `resolution` yourself, it takes precedence over the value inferred from `size`.

### Size to aspect ratio (reference)

LiteLLM automatically converts size dimensions to Veo's aspect ratio format:
- `"1280x720"`, `"1920x1080"` → `"16:9"` (landscape)
- `"720x1280"`, `"1080x1920"` → `"9:16"` (portrait)

Expand Down Expand Up @@ -293,7 +313,14 @@ with open("video.mp4", "wb") as f:
</TabItem>
</Tabs>

## Cost Tracking
## Cost tracking and spend

LiteLLM estimates **video spend** from:

1. **How long** the generated clip is billed for (seconds), and
2. **The per-second price** for that model in LiteLLM’s model catalog (aligned with [Google’s Gemini API video pricing](https://ai.google.dev/gemini-api/docs/video) where applicable).

Some models charge **different per-second rates** for **720p** vs **1080p**. When you use the standard `size` presets above (or set `resolution` explicitly), LiteLLM uses the matching tier so **proxy spend, logs, and budgets** line up with the resolution you requested.

LiteLLM automatically tracks costs for Veo video generation:

Expand All @@ -314,8 +341,8 @@ response = litellm.video_generation(
| Feature | OpenAI (Sora) | Gemini (Veo) |
|---------|---------------|--------------|
| Reference Images | ✅ Supported | ❌ Not supported |
| Size Control | ✅ Supported | ❌ Not supported |
| Duration Control | ✅ Supported | ❌ Not supported |
| Size / dimensions | ✅ Supported | ✅ Supported via `size` → aspect ratio + `720p`/`1080p` where preset |
| Duration (`seconds`) | ✅ Supported | ✅ Supported (maps to `durationSeconds`; limits per Google docs) |
| Video Remix/Edit | ✅ Supported | ❌ Not supported |
| Video List | ✅ Supported | ❌ Not supported |
| Prompt-based Generation | ✅ Supported | ✅ Supported |
Expand Down
23 changes: 17 additions & 6 deletions litellm/cost_calculator.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,10 @@
cost_per_token as lemonade_cost_per_token,
)
from litellm.llms.openai.cost_calculation import (
_video_output_cost_per_second,
Comment thread
Sameerlite marked this conversation as resolved.
Dismissed
cost_per_second as openai_cost_per_second,
cost_per_token as openai_cost_per_token,
Comment thread
Sameerlite marked this conversation as resolved.
Dismissed
)
from litellm.llms.openai.cost_calculation import cost_per_token as openai_cost_per_token
from litellm.llms.perplexity.cost_calculator import (
cost_per_token as perplexity_cost_per_token,
)
Expand Down Expand Up @@ -1144,15 +1145,16 @@ def completion_cost( # noqa: PLR0915
if isinstance(usage_obj, BaseModel) and not _is_known_usage_objects(
usage_obj=usage_obj
):
_usage_for_dump = cast(BaseModel, usage_obj)
setattr(
completion_response,
"usage",
litellm.Usage(**usage_obj.model_dump()),
litellm.Usage(**_usage_for_dump.model_dump()),
)
if usage_obj is None:
_usage = {}
elif isinstance(usage_obj, BaseModel):
_usage = usage_obj.model_dump()
_usage = cast(BaseModel, usage_obj).model_dump()
else:
_usage = usage_obj

Expand Down Expand Up @@ -1279,14 +1281,20 @@ def completion_cost( # noqa: PLR0915
_video_model_info = _metadata.get("model_info", None)

usage_obj = getattr(completion_response, "usage", None)
duration_seconds: Optional[float] = None
video_resolution: Optional[str] = None
if completion_response is not None and usage_obj:
# Handle both dict and Pydantic Usage object
if isinstance(usage_obj, dict):
duration_seconds = usage_obj.get("duration_seconds", None)
_vr = usage_obj.get("video_resolution", None)
else:
duration_seconds = getattr(
usage_obj, "duration_seconds", None
)
_vr = getattr(usage_obj, "video_resolution", None)
if _vr is not None:
video_resolution = str(_vr).strip().lower()

if duration_seconds is not None:
# Calculate cost based on video duration using video-specific cost calculation
Expand All @@ -1299,13 +1307,15 @@ def completion_cost( # noqa: PLR0915
duration_seconds=duration_seconds,
custom_llm_provider=custom_llm_provider,
model_info=_video_model_info,
video_resolution=video_resolution,
)
# Fallback to default video cost calculation if no duration available
return default_video_cost_calculator(
model=model,
duration_seconds=0.0, # Default to 0 if no duration available
custom_llm_provider=custom_llm_provider,
model_info=_video_model_info,
video_resolution=video_resolution,
)
elif call_type in _SPEECH_CALL_TYPES:
prompt_characters = litellm.utils._count_characters(text=prompt)
Expand Down Expand Up @@ -1626,7 +1636,7 @@ def get_response_cost_from_hidden_params(
hidden_params: Union[dict, BaseModel],
) -> Optional[float]:
if isinstance(hidden_params, BaseModel):
_hidden_params_dict = hidden_params.model_dump()
_hidden_params_dict = cast(BaseModel, hidden_params).model_dump()
else:
_hidden_params_dict = hidden_params

Expand Down Expand Up @@ -1963,6 +1973,7 @@ def default_video_cost_calculator(
duration_seconds: float,
custom_llm_provider: Optional[str] = None,
model_info: Optional[ModelInfo] = None,
video_resolution: Optional[str] = None,
) -> float:
"""
Default video cost calculator for video generation
Expand All @@ -1974,6 +1985,7 @@ def default_video_cost_calculator(
model_info (Optional[ModelInfo]): Deployment-level model info containing
custom video pricing. When provided, used before falling back to
the global litellm.model_cost lookup.
video_resolution (Optional[str]): From usage (e.g. ``720p``, ``1080p``) for tiered per-second pricing.

Returns:
float: Cost in USD for the video generation
Expand Down Expand Up @@ -2027,8 +2039,7 @@ def default_video_cost_calculator(
if video_cost_per_second is not None:
return video_cost_per_second * duration_seconds

# Fallback to general output cost per second
output_cost_per_second = cost_info.get("output_cost_per_second")
output_cost_per_second = _video_output_cost_per_second(cost_info, video_resolution)
if output_cost_per_second is not None:
return output_cost_per_second * duration_seconds

Expand Down
57 changes: 48 additions & 9 deletions litellm/llms/gemini/videos/transformation.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,16 @@ def _convert_image_to_gemini_format(image_file) -> Dict[str, str]:
return {"bytesBase64Encoded": base64_encoded, "mimeType": mime_type}


def _usage_video_resolution_from_parameters(
parameters: Dict[str, Any]
) -> Optional[str]:
"""Normalize Veo ``parameters.resolution`` for usage and cost tracking."""
res = parameters.get("resolution")
if res is None or res == "":
return None
return str(res).strip().lower()


class GeminiVideoConfig(BaseVideoConfig):
"""
Configuration class for Gemini (Veo) video generation.
Expand All @@ -65,6 +75,13 @@ class GeminiVideoConfig(BaseVideoConfig):
4. Download video using file API
"""

_OPENAI_VIDEO_SIZE_TO_ASPECT_RATIO: Dict[str, str] = {
"1280x720": "16:9",
"1920x1080": "16:9",
"720x1280": "9:16",
"1080x1920": "9:16",
}
Comment thread
Sameerlite marked this conversation as resolved.

def __init__(self):
super().__init__()

Expand All @@ -88,6 +105,8 @@ def map_openai_params(
- prompt → prompt
- input_reference → image
- size → aspectRatio (e.g., "1280x720" → "16:9")
- size → resolution when inferable ("1280x720"/"720x1280" → "720p",
"1920x1080"/"1080x1920" → "1080p"); skipped if ``resolution`` is already set
- seconds → durationSeconds (defaults to 4 seconds if not provided)

All other params are passed through as-is to support Gemini-specific parameters.
Expand All @@ -113,6 +132,10 @@ def map_openai_params(
aspect_ratio = self._convert_size_to_aspect_ratio(size)
if aspect_ratio:
mapped_params["aspectRatio"] = aspect_ratio
if not video_create_optional_params.get("resolution"):
inferred_resolution = self._convert_size_to_resolution(size)
if inferred_resolution is not None:
mapped_params["resolution"] = inferred_resolution

# Map seconds to durationSeconds, default to 4 seconds (matching OpenAI)
if "seconds" in video_create_optional_params:
Expand Down Expand Up @@ -143,14 +166,27 @@ def _convert_size_to_aspect_ratio(self, size: str) -> Optional[str]:
if not size:
return None

aspect_ratio_map = {
"1280x720": "16:9",
"1920x1080": "16:9",
"720x1280": "9:16",
"1080x1920": "9:16",
}
return self._OPENAI_VIDEO_SIZE_TO_ASPECT_RATIO.get(size, "16:9")

def _convert_size_to_resolution(self, size: str) -> Optional[str]:
"""
Map OpenAI ``size`` (WxH) to Veo ``resolution`` for presets in
``_OPENAI_VIDEO_SIZE_TO_ASPECT_RATIO`` (720p / 1080p from the smaller edge).

return aspect_ratio_map.get(size, "16:9")
Unknown sizes return None so the API default applies (no forced resolution).
"""
if not size or size not in self._OPENAI_VIDEO_SIZE_TO_ASPECT_RATIO:
return None
try:
w_str, h_str = size.split("x", 1)
smaller = min(int(w_str), int(h_str))
except (ValueError, TypeError):
return None
if smaller == 720:
return "720p"
if smaller == 1080:
return "1080p"
return None

def validate_environment(
self,
Expand Down Expand Up @@ -279,7 +315,7 @@ def transform_video_create_response(
We return this as a VideoObject with:
- id: operation name (used for polling)
- status: "processing"
- usage: includes duration_seconds for cost calculation
- usage: includes duration_seconds and optional video_resolution for cost calculation
"""
response_data = raw_response.json()

Expand Down Expand Up @@ -307,7 +343,7 @@ def transform_video_create_response(
model=model,
)

usage_data = {}
usage_data: Dict[str, Any] = {}
if request_data:
parameters = request_data.get("parameters", {})
duration = (
Expand All @@ -319,6 +355,9 @@ def transform_video_create_response(
usage_data["duration_seconds"] = float(duration)
except (ValueError, TypeError):
pass
video_resolution = _usage_video_resolution_from_parameters(parameters)
if video_resolution is not None:
usage_data["video_resolution"] = video_resolution

video_obj.usage = usage_data
return video_obj
Expand Down
50 changes: 47 additions & 3 deletions litellm/llms/openai/cost_calculation.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
- e.g.: prompt caching
"""

from typing import Literal, Optional, Tuple
from typing import Any, Literal, Mapping, Optional, Tuple

from litellm._logging import verbose_logger
from litellm.litellm_core_utils.llm_cost_calc.utils import generic_cost_per_token
Expand Down Expand Up @@ -128,11 +128,55 @@ def cost_per_second(
return prompt_cost, completion_cost


def _video_resolution_to_cost_field_suffix(resolution: str) -> Optional[str]:
"""
Map usage resolution to a safe suffix for ``output_cost_per_second_<suffix>`` keys.

Note: Currently only ``output_cost_per_second_1080p`` is explicitly declared in
ModelInfo (types/utils.py). Other resolution tiers (e.g., 720p, 4k) can be added
to model_prices_and_context_window.json but are not exposed via get_model_info()
until added to the ModelInfo TypedDict.
"""
r = resolution.strip().lower()
if not r:
return None
safe = "".join(c for c in r if c.isalnum() or c == "_")
if not safe or len(safe) > 24:
return None
return safe
Comment thread
Sameerlite marked this conversation as resolved.


def _video_output_cost_per_second(
model_info: Mapping[str, Any],
video_resolution: Optional[str],
) -> Optional[float]:
"""
Per-second video output rate from model_info.

If ``video_resolution`` is set (e.g. ``1080p``, ``720p``, ``4k``), looks up
``output_cost_per_second_<resolution>`` first (e.g. ``output_cost_per_second_1080p``),
then falls back to ``output_cost_per_second``.
"""
r = (video_resolution or "").strip().lower()
if r:
suffix = _video_resolution_to_cost_field_suffix(r)
if suffix is not None:
tier_key = f"output_cost_per_second_{suffix}"
tier_rate = model_info.get(tier_key)
if tier_rate is not None:
return float(tier_rate)
out = model_info.get("output_cost_per_second")
if out is not None:
return float(out)
return None


def video_generation_cost(
model: str,
duration_seconds: float,
custom_llm_provider: Optional[str] = None,
model_info: Optional[ModelInfo] = None,
video_resolution: Optional[str] = None,
) -> float:
"""
Calculates the cost for video generation based on duration in seconds.
Expand All @@ -144,6 +188,7 @@ def video_generation_cost(
- model_info: Optional[dict], deployment-level model info containing
custom video pricing. When provided, skips the global
get_model_info() lookup so that deployment-specific pricing is used.
- video_resolution: Optional resolution label from usage (e.g. ``720p``, ``1080p``).

Returns:
float - total_cost_in_usd
Expand All @@ -162,8 +207,7 @@ def video_generation_cost(
)
return video_cost_per_second * duration_seconds

# Fallback to general output cost per second
output_cost_per_second = model_info.get("output_cost_per_second")
output_cost_per_second = _video_output_cost_per_second(model_info, video_resolution)
if output_cost_per_second is not None:
verbose_logger.debug(
f"For model={model} - output_cost_per_second: {output_cost_per_second}; duration: {duration_seconds}"
Expand Down
Loading
Loading