> ## Documentation Index
> Fetch the complete documentation index at: https://documentation.api.odyssey.ml/llms.txt
> Use this file to discover all available pages before exploring further.

# Recordings

> Working with stream recordings in the Odyssey Python client.

<Info>
  Recording features are available in the Python SDK.
</Info>

After a stream session ends, you can retrieve recording artifacts including the full video, events log, thumbnail, and preview.

## Capturing the Stream ID

The stream ID is provided in the `on_stream_started` callback or returned from `start_stream()`. Save this ID to retrieve recordings later:

```python theme={null}
import asyncio
from odyssey import Odyssey

client = Odyssey(api_key="ody_your_api_key_here")

current_stream_id = None

def on_stream_started(stream_id: str) -> None:
    global current_stream_id
    current_stream_id = stream_id
    print(f"Stream started: {stream_id}")

async def main():
    await client.connect(
        on_video_frame=lambda frame: None,
        on_stream_started=on_stream_started,
    )

    # Or capture from return value
    stream_id = await client.start_stream("A cat")

    # ... interact with the stream ...
    await client.end_stream()
    await client.disconnect()

asyncio.run(main())
```

## Retrieving a Recording

Use `get_recording()` with the stream ID to get presigned URLs for the recording artifacts:

```python theme={null}
recording = await client.get_recording(current_stream_id)

if recording.video_url:
    print(f"Video URL: {recording.video_url}")
    print(f"Duration: {recording.duration_seconds}s")
    print(f"Frames: {recording.frame_count}")

if recording.events_url:
    # Download and parse the events log (JSONL format)
    import httpx
    import json

    async with httpx.AsyncClient() as http:
        response = await http.get(recording.events_url)
        events = [json.loads(line) for line in response.text.strip().split('\n')]
        print(f"Session events: {events}")
```

<Note>
  `get_recording()` can be called without an active connection. It only requires a valid API key.
</Note>

### Recording Properties

| Property           | Type            | Description                            |
| ------------------ | --------------- | -------------------------------------- |
| `stream_id`        | `str`           | The stream ID                          |
| `video_url`        | `str \| None`   | Presigned URL for full recording (MP4) |
| `events_url`       | `str \| None`   | Presigned URL for events log (JSONL)   |
| `thumbnail_url`    | `str \| None`   | Presigned URL for thumbnail (JPEG)     |
| `preview_url`      | `str \| None`   | Presigned URL for preview video (MP4)  |
| `frame_count`      | `int \| None`   | Total frames in recording              |
| `duration_seconds` | `float \| None` | Recording duration in seconds          |

## Listing All Recordings

Use `list_stream_recordings()` to get a paginated list of all your recordings:

```python theme={null}
# Get recent recordings
result = await client.list_stream_recordings(limit=10)
print(f"Found {result.total} recordings")

for rec in result.recordings:
    print(f"Stream {rec.stream_id}: {rec.duration_seconds}s at {rec.width}x{rec.height}")

# Paginate through results
page2 = await client.list_stream_recordings(limit=10, offset=10)
```

### Pagination Options

| Option   | Type          | Default | Description                  |
| -------- | ------------- | ------- | ---------------------------- |
| `limit`  | `int \| None` | `None`  | Maximum recordings to return |
| `offset` | `int \| None` | `None`  | Number of recordings to skip |

## Complete Workflow Example

This example shows the full workflow: starting a stream, interacting with it, ending it, and then retrieving the recording:

```python theme={null}
import asyncio
from odyssey import Odyssey, OdysseyConnectionError

async def main():
    client = Odyssey(api_key="ody_your_api_key_here")

    try:
        await client.connect(
            on_video_frame=lambda frame: print(f"Frame: {frame.width}x{frame.height}"),
        )

        # Start an interactive stream and capture the stream ID
        stream_id = await client.start_stream("A cat sitting on a windowsill", portrait=True)
        print(f"Stream started: {stream_id}")

        # Send some interactions
        await client.interact("The cat stretches and yawns")
        await client.interact("The cat looks outside at birds")

        # Let it run for a bit
        await asyncio.sleep(5)

        # End the stream
        await client.end_stream()

    except OdysseyConnectionError as e:
        print(f"Connection failed: {e}")
    finally:
        await client.disconnect()

    # Retrieve the recording (can be done after disconnect)
    recording = await client.get_recording(stream_id)

    if recording.video_url:
        print(f"Recording available at: {recording.video_url}")
        print(f"Duration: {recording.duration_seconds} seconds")

asyncio.run(main())
```

## Downloading Recordings

Example of downloading recording files:

```python theme={null}
import httpx
from pathlib import Path

async def download_recording(client: Odyssey, stream_id: str, output_dir: Path):
    recording = await client.get_recording(stream_id)

    async with httpx.AsyncClient() as http:
        if recording.video_url:
            response = await http.get(recording.video_url)
            (output_dir / f"{stream_id}.mp4").write_bytes(response.content)
            print(f"Downloaded video: {stream_id}.mp4")

        if recording.thumbnail_url:
            response = await http.get(recording.thumbnail_url)
            (output_dir / f"{stream_id}_thumb.jpg").write_bytes(response.content)
            print(f"Downloaded thumbnail: {stream_id}_thumb.jpg")
```

## Broadcast & Recordings

Broadcast streams are recorded the same way as standard interactive streams.
Recordings are tied to the **stream ID**, not to individual spectators.

* Starting a stream with `broadcast=True` does not change how recordings are created.
* Spectators do not generate separate recordings.
* Use the `stream_id` returned from `start_stream()` (or received via `on_stream_started`) to retrieve artifacts with `get_recording()`.

## Related

<CardGroup cols={2}>
  <Card icon="code" href="/sdk/python/types#recording" title="Recording Types">
    Python dataclasses for recordings
  </Card>

  <Card icon="cube" href="/sdk/python/client#get_recording" title="Odyssey Client">
    get\_recording and list\_stream\_recordings methods
  </Card>
</CardGroup>
