Skip to main content

Video Types

VideoFrame

Video frame data received from the stream.
@dataclass(frozen=True, slots=True)
class VideoFrame:
    data: np.ndarray      # RGB uint8 array, shape (height, width, 3)
    width: int            # Frame width in pixels
    height: int           # Frame height in pixels
    timestamp_ms: int     # Presentation timestamp in milliseconds
PropertyTypeDescription
datanp.ndarrayRGB uint8 array with shape (height, width, 3)
widthintFrame width in pixels
heightintFrame height in pixels
timestamp_msintPresentation timestamp in milliseconds
Example usage:
import cv2
from PIL import Image

def on_frame(frame: VideoFrame) -> None:
    # OpenCV (note: OpenCV uses BGR)
    cv2.imshow("video", cv2.cvtColor(frame.data, cv2.COLOR_RGB2BGR))

    # PIL
    image = Image.fromarray(frame.data)

    # Headless processing
    processed = some_ml_model(frame.data)

Recording Types

Recording

Recording data with presigned URLs for a stream.
@dataclass(frozen=True, slots=True)
class Recording:
    stream_id: str              # Unique stream identifier
    video_url: str | None       # Presigned URL for video file
    events_url: str | None      # Presigned URL for events JSON
    thumbnail_url: str | None   # Presigned URL for thumbnail image
    preview_url: str | None     # Presigned URL for preview video
    frame_count: int | None     # Total number of frames
    duration_seconds: float | None  # Duration in seconds
PropertyTypeDescription
stream_idstrUnique stream identifier
video_urlstr | NonePresigned URL for video file (MP4)
events_urlstr | NonePresigned URL for events log (JSONL)
thumbnail_urlstr | NonePresigned URL for thumbnail image (JPEG)
preview_urlstr | NonePresigned URL for preview video (MP4)
frame_countint | NoneTotal number of frames
duration_secondsfloat | NoneDuration in seconds
URLs are valid for a limited time (typically 1 hour).

StreamRecordingInfo

Summary info for a stream recording in a list.
@dataclass(frozen=True, slots=True)
class StreamRecordingInfo:
    stream_id: str              # Unique stream identifier
    width: int                  # Video width in pixels
    height: int                 # Video height in pixels
    started_at: str             # ISO 8601 timestamp
    ended_at: str | None        # ISO 8601 timestamp or None if active
    duration_seconds: float | None  # Duration in seconds
PropertyTypeDescription
stream_idstrUnique stream identifier
widthintVideo width in pixels
heightintVideo height in pixels
started_atstrISO 8601 timestamp when stream started
ended_atstr | NoneISO 8601 timestamp when stream ended
duration_secondsfloat | NoneDuration in seconds

StreamRecordingsList

Paginated list of stream recordings.
@dataclass(frozen=True, slots=True)
class StreamRecordingsList:
    recordings: list[StreamRecordingInfo]  # List of recording info
    total: int                              # Total recordings available
    limit: int                              # Max per request
    offset: int                             # Recordings skipped
PropertyTypeDescription
recordingslist[StreamRecordingInfo]List of recording summaries
totalintTotal recordings available
limitintLimit used in request
offsetintOffset used in request

Simulate API Types

Simulate API types were added in v1.0.0

ScriptEntry

An entry in a simulation script.
from typing import TypedDict, NotRequired

class StartAction(TypedDict):
    prompt: str
    image: NotRequired[str | bytes]  # Optional image for image-to-video

class InteractAction(TypedDict):
    prompt: str

class ScriptEntry(TypedDict):
    timestamp_ms: int                    # When this action occurs (milliseconds from start)
    start: NotRequired[StartAction]      # Begin a new stream
    interact: NotRequired[InteractAction] # Send an interaction
    end: NotRequired[dict]               # End the stream (empty dict)
PropertyTypeDescription
timestamp_msintWhen this action occurs (milliseconds from start)
start{ prompt: str, image?: str | bytes }Begin a new stream with initial prompt
interact{ prompt: str }Send an interaction prompt
end{}End the current stream (empty dict)

SimulationStream

Output stream from a simulation job, including recording artifact URLs.
@dataclass(frozen=True, slots=True)
class SimulationStream:
    stream_id: str                    # Unique stream identifier
    video_url: str | None             # Presigned URL for the video file
    events_url: str | None            # Presigned URL for the events JSON
    thumbnail_url: str | None         # Presigned URL for the thumbnail image
    preview_url: str | None           # Presigned URL for the preview video
    frame_count: int | None           # Total number of frames
    duration_seconds: float | None    # Duration of the video in seconds
    script_index: int                 # Index of the script in batch mode (0 for single script)
PropertyTypeDescription
stream_idstrUnique stream identifier
video_urlstr | NonePresigned URL for the video file (MP4)
events_urlstr | NonePresigned URL for the events log (JSONL)
thumbnail_urlstr | NonePresigned URL for the thumbnail image (JPEG)
preview_urlstr | NonePresigned URL for the preview video (MP4)
frame_countint | NoneTotal number of frames in the video
duration_secondsfloat | NoneDuration of the video in seconds
script_indexintIndex of the script in batch mode (0 for single script)

SimulationJobInfo

Summary information for a simulation job in a list.
@dataclass(frozen=True, slots=True)
class SimulationJobInfo:
    job_id: str                       # Unique identifier for the job
    status: str                       # Current job status
    priority: str                     # Job priority
    created_at: str                   # ISO timestamp when job was created
    completed_at: str | None          # ISO timestamp when job completed
    error_message: str | None         # Error message if job failed
PropertyTypeDescription
job_idstrUnique identifier for the job
statusstrCurrent job status (pending, dispatched, processing, completed, failed, cancelled)
prioritystrJob priority
created_atstrISO timestamp when job was created
completed_atstr | NoneISO timestamp when job completed
error_messagestr | NoneError message if job failed

SimulationJobDetail

Detailed information about a simulation job.
@dataclass(frozen=True, slots=True)
class SimulationJobDetail:
    job_id: str                       # Unique identifier for the job
    status: SimulationJobStatus       # Current job status
    priority: str                     # Job priority
    created_at: str                   # ISO timestamp when job was created
    dispatched_at: str | None         # ISO timestamp when job was dispatched
    started_at: str | None            # ISO timestamp when processing started
    completed_at: str | None          # ISO timestamp when job completed
    error_message: str | None         # Error message if job failed
    assigned_region: str | None       # Region where the job is being processed
    retry_count: int                  # Number of times the job has been retried
    streams: list[SimulationStream]   # Output streams from the simulation
    estimated_wait_minutes: float | None  # Estimated wait time in minutes
PropertyTypeDescription
job_idstrUnique identifier for the job
statusSimulationJobStatusCurrent job status (pending, dispatched, processing, completed, failed, cancelled)
prioritystrJob priority
created_atstrISO timestamp when job was created
dispatched_atstr | NoneISO timestamp when the job was dispatched to a worker
started_atstr | NoneISO timestamp when processing started
completed_atstr | NoneISO timestamp when job completed
error_messagestr | NoneError message if job failed
assigned_regionstr | NoneRegion where the job is being processed
retry_countintNumber of times the job has been retried
streamslist[SimulationStream]Output streams from the simulation
estimated_wait_minutesfloat | NoneEstimated wait time in minutes

SimulationJobsList

Paginated list of simulation jobs.
@dataclass(frozen=True, slots=True)
class SimulationJobsList:
    jobs: list[SimulationJobInfo]     # List of simulation job summaries
    total: int                        # Total jobs available
    limit: int                        # Limit used in request
    offset: int                       # Offset used in request
PropertyTypeDescription
jobslist[SimulationJobInfo]List of simulation job summaries
totalintTotal jobs available
limitintLimit used in request
offsetintOffset used in request

Client Credentials Types

Client credentials types were added in v1.3.0

StreamerCapabilities

Capabilities advertised by the connected streamer session. Used to determine what features are available (e.g., image-to-video).
@dataclass(frozen=True, slots=True)
class StreamerCapabilities:
    image_to_video: bool = False

    def to_dict(self) -> dict[str, bool]
    @classmethod
    def from_dict(cls, data: dict) -> StreamerCapabilities
PropertyTypeDefaultDescription
image_to_videoboolFalseWhether the streamer supports image-to-video generation

ClientCredentials

Pre-minted credentials for client-side connections without an API key. Created server-side via create_client_credentials() and passed to the client application.
@dataclass(frozen=True, slots=True)
class ClientCredentials:
    signaling_url: str
    session_token: str
    expires_in: int
    capabilities: StreamerCapabilities | None = None
    session_id: str  # auto-derived from JWT, not user-provided

    def to_dict(self) -> dict[str, Any]
    @classmethod
    def from_dict(cls, data: dict) -> ClientCredentials
PropertyTypeDescription
signaling_urlstrWebSocket URL for the signaling server
session_tokenstrShort-lived JWT for session authentication
expires_inintToken lifetime in seconds
capabilitiesStreamerCapabilities | NoneStreamer capabilities (e.g., image-to-video support)
session_idstrDerived from the JWT payload — do not provide manually
Serialization:
# Server-side: mint and serialize
creds = await server.create_client_credentials()
creds_dict = creds.to_dict()
# Send creds_dict as JSON to the client

# Client-side: deserialize
from odyssey import ClientCredentials
creds = ClientCredentials.from_dict(data_from_server)
The session_id in the dict is informational. On deserialization, it is re-derived from the JWT to prevent tampering.

Broadcast Types

BroadcastInfo

Broadcast playback URLs for spectators, received via the on_broadcast_ready callback when broadcast=True is passed to start_stream().
@dataclass(frozen=True, slots=True)
class BroadcastInfo:
   webrtc_url: str
   spectator_token: str
   hls_url: str | None = None
PropertyTypeDescription
webrtc_urlstrWebRTC (WHEP) endpoint URL for spectator playback.
spectator_tokenstrToken required to authenticate a spectator connection.
hls_urlstr | NoneOptional HLS playback URL, if available.

SpectatorConnection

Handle for an active spectator connection. Returned by connect_to_stream().
@dataclass(frozen=True, slots=True)
class SpectatorConnection:
   @property
   def is_connected(self) -> bool
   @property
   def peer_connection(self) -> object | None
   async def disconnect(self) -> None
PropertyTypeDescription
is_connectedboolIndicates whether the spectator connection is currently active.
peer_connectionObject | NoneThe underlying RTCPeerConnection. Can be used to retrieve WebRTC statistics (e.g., via getStats()).
MethodDescription
disconnectAsynchronously close the spectator connection and release resources.
from odyssey import connect_to_stream

def handle_frame(frame):
   cv2.imshow("Broadcast", frame.data)
   cv2.waitKey(1)

connection = await connect_to_stream(
   webrtc_url="http://localhost:8889/live/stream123",
   spectator_token="spectator_abc...",
   on_video_frame=handle_frame,
)

# Later
await connection.disconnect()

Status Types

ConnectionStatus

class ConnectionStatus(str, Enum):
    AUTHENTICATING = "authenticating"  # Authenticating with Odyssey API
    CONNECTING = "connecting"          # Connecting to signaling server
    RECONNECTING = "reconnecting"      # Reconnecting after disconnect
    CONNECTED = "connected"            # Connected and ready
    DISCONNECTED = "disconnected"      # Disconnected (clean)
    FAILED = "failed"                  # Connection failed (fatal)

Configuration Types

ClientConfig

Configuration for the Odyssey client.
@dataclass
class ClientConfig:
    api_key: str                        # API key for authentication (required)
    api_url: str = "https://api.odyssey.ml"  # API URL
    debug: bool = False                 # Enable debug logging
    advanced: AdvancedConfig = AdvancedConfig()  # Advanced settings

AdvancedConfig

Advanced connection settings.
@dataclass
class AdvancedConfig:
    max_retries: int = 5              # Max retry attempts
    initial_retry_delay_ms: int = 1000  # Initial retry delay
    max_retry_delay_ms: int = 2000    # Max retry delay
    retry_backoff_multiplier: float = 2.0  # Backoff multiplier
    queue_timeout_s: int = 30         # Queue timeout in seconds

Exceptions

OdysseyError

Base exception for all Odyssey errors.
class OdysseyError(Exception):
    pass

OdysseyAuthError

Raised when authentication fails.
class OdysseyAuthError(OdysseyError):
    pass

OdysseyConnectionError

Raised when connection fails.
class OdysseyConnectionError(OdysseyError):
    pass

OdysseyStreamError

Raised when a stream operation fails.
class OdysseyStreamError(OdysseyError):
    pass

Error Handling

Fatal vs Non-Fatal Errors

The on_error handler receives a fatal boolean parameter:
FatalDescriptionAction Required
TrueConnection cannot continueReconnect or exit
FalseRecoverable errorMay retry or notify user

Common Errors

ErrorDescription
OdysseyAuthErrorAPI key is invalid or expired
OdysseyConnectionError: No streamers availableNo streamers available, try again later
OdysseyConnectionError: Timed out waiting for a streamerQueue timeout expired
OdysseyStreamError: Cannot start stream: client is disconnectedAttempted operation while disconnected

Environment Variables

VariableDescription
ODYSSEY_API_URLOverride default API URL
ODYSSEY_API_KEYDefault API key (used by examples)