From bbd6788a47696434ab679760541ab844cc6eb50f Mon Sep 17 00:00:00 2001 From: Ben Carr Date: Sun, 17 May 2026 20:32:54 +0100 Subject: [PATCH 1/2] Add session video dimensions options --- src/anam/types.py | 17 ++++++++++++++++ tests/test_client.py | 47 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+) diff --git a/src/anam/types.py b/src/anam/types.py index 5dc5076..b82444b 100644 --- a/src/anam/types.py +++ b/src/anam/types.py @@ -205,11 +205,15 @@ class SessionOptions: Args: enable_session_replay: If True (default), session is recorded. Set False to disable. video_quality: Video quality profile to pin the video quality. Supported values are "high" (default) and "auto". + video_width: Requested video output width. Must be provided with video_height. + video_height: Requested video output height. Must be provided with video_width. egress: Optional direct egress to a third-party transport (e.g. Daily). See :class:`EgressOptions`. """ enable_session_replay: bool = True video_quality: Literal["high", "auto"] = "high" + video_width: int | None = None + video_height: int | None = None egress: EgressOptions | None = None def __post_init__(self) -> None: @@ -218,12 +222,25 @@ def __post_init__(self) -> None: ) if self.video_quality not in {"high", "auto"}: raise ValueError('video_quality must be either "high" or "auto"') + if (self.video_width is None) != (self.video_height is None): + raise ValueError("video_width and video_height must be provided together") + for name, value in ( + ("video_width", self.video_width), + ("video_height", self.video_height), + ): + if value is not None and ( + isinstance(value, bool) or not isinstance(value, int) or value <= 0 + ): + raise ValueError(f"{name} must be a positive integer") def to_dict(self) -> dict[str, Any]: result: dict[str, Any] = { "sessionReplay": self._session_replay.to_dict(), "videoQuality": self.video_quality, } + if self.video_width is not None and self.video_height is not None: + result["videoWidth"] = self.video_width + result["videoHeight"] = self.video_height if self.egress is not None: result["egress"] = self.egress.to_dict() return result diff --git a/tests/test_client.py b/tests/test_client.py index 8945f83..4c88d2d 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -2,6 +2,7 @@ import json import math +from typing import Any from unittest.mock import AsyncMock, MagicMock import pytest @@ -271,10 +272,56 @@ def test_to_dict_with_video_quality_auto(self) -> None: "videoQuality": "auto", } + def test_to_dict_with_video_dimensions(self) -> None: + options = SessionOptions(video_width=1152, video_height=768) + result = options.to_dict() + + assert result == { + "sessionReplay": {"enableSessionReplay": True}, + "videoQuality": "high", + "videoWidth": 1152, + "videoHeight": 768, + } + def test_invalid_video_quality_raises_value_error(self) -> None: with pytest.raises(ValueError, match='video_quality must be either "high" or "auto"'): SessionOptions(video_quality="medium") # type: ignore[arg-type] + @pytest.mark.parametrize( + "kwargs", + [ + {"video_width": 1152}, + {"video_height": 768}, + ], + ) + def test_video_dimensions_must_be_provided_together(self, kwargs: dict[str, Any]) -> None: + with pytest.raises( + ValueError, + match="video_width and video_height must be provided together", + ): + SessionOptions(**kwargs) + + @pytest.mark.parametrize( + ("field", "value"), + [ + ("video_width", 0), + ("video_width", -1), + ("video_width", 1.5), + ("video_width", True), + ("video_height", 0), + ("video_height", -1), + ("video_height", 1.5), + ("video_height", True), + ], + ) + def test_video_dimensions_must_be_positive_integers( + self, field: str, value: int | float | bool + ) -> None: + kwargs: dict[str, Any] = {"video_width": 1152, "video_height": 768, field: value} + + with pytest.raises(ValueError, match=f"{field} must be a positive integer"): + SessionOptions(**kwargs) # type: ignore[arg-type] + class TestDirectorNoteCue: """Tests for Session.send_director_note_cue.""" From 36f7cd9bba477008d8f5cb4b40be0d37f76289dd Mon Sep 17 00:00:00 2001 From: sebvanleuven Date: Tue, 21 Jul 2026 13:37:47 +0100 Subject: [PATCH 2/2] docs: add output video dimensions section with resolution table Document supported Cara-3/Cara-4 landscape and portrait sizes with examples for both orientations, aligned with Agora session docs. Co-authored-by: Cursor --- README.md | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/README.md b/README.md index 994cd2e..72d34b4 100644 --- a/README.md +++ b/README.md @@ -145,6 +145,36 @@ async with client.connect(session_options=session_options) as session: **Daily tokens.** Mint meeting tokens through your own Daily app (Daily REST API or a server you control); the SDK never does this for you. A Daily meeting token is bound to the room it was minted for, so the `token` you pass must be minted for the same `room_url` — passing a token minted for a different room will fail at join time with a 403. For public rooms (no token required) you can omit `token` entirely. +## Output Video Dimensions + +Optional. Request a specific output resolution via `video_width` / `video_height` on `SessionOptions`. Both must be set together (or both left unset); unsupported model/resolution combinations are rejected by the API. When unset, output defaults to landscape for the avatar model. + +| avatar model | orientation | width | height | +|--------------|-------------|-------|--------| +| Cara-3 | landscape | 720 | 480 | +| Cara-4 | landscape | 1152 | 768 | +| Cara-4 | portrait | 768 | 1152 | + +Landscape (Cara-4 default): + +```python +from anam import SessionOptions + +session_options = SessionOptions(video_width=1152, video_height=768) +async with client.connect(session_options=session_options) as session: + ... +``` + +Portrait (Cara-4 only — the avatar must be on a portrait-capable Cara-4 model): + +```python +from anam import SessionOptions + +session_options = SessionOptions(video_width=768, video_height=1152) +async with client.connect(session_options=session_options) as session: + ... +``` + ## API Reference ### AnamClient