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

# Storage & Search

> Optional persistence, export workflows, and semantic search for captured content

Capture sessions can optionally persist media for later search and playback. Control storage per-channel and access exported assets.

<Note>
  Desktop capture currently supports **macOS** and **Windows**.
</Note>

## Quick Example

<CodeGroup>
  ```python Python theme={null}
  # After capture_session.exported webhook
  cap = conn.get_capture_session("cap-xxx")

  # Get the muxed video
  video_id = cap.exported_video_id
  video = coll.get_video(video_id)

  # Search the captured content
  understanding = video.understand(
      analyzers=[{"type": "spoken_words", "name": "transcript"}],
  )
  understanding.wait_until_complete()
  transcript_analyzer = understanding.get_analyzer("transcript")
  transcript_index = video.index(
      name="transcript",
      source=transcript_analyzer,
      use_for=["semantic"],
      fields={"semantic": ["text"]},
  )
  transcript_index.wait_until_complete()

  results = video.semantic_search(
      query="budget discussion",
      index_ids=[transcript_index.index_id],
      top_k=5,
  )
  for shot in results.get_shots():
      print(f"{shot.start}s: {shot.text}")
      shot.play()
  ```

  ```javascript Node.js theme={null}
  // After capture_session.exported webhook
  const cap = await conn.getCaptureSession("cap-xxx");

  // Get the muxed video
  const videoId = cap.exportedVideoId;
  const video = await coll.getVideo(videoId);

  // Search the captured content
  const understanding = await video.understand([
    { type: "spoken_words", name: "transcript" },
  ]);
  await understanding.waitUntilComplete();
  const transcriptAnalyzer = await understanding.getAnalyzer("transcript");
  const transcriptIndex = await video.index(transcriptAnalyzer, {
    name: "transcript",
    useFor: ["semantic"],
    fields: { semantic: ["text"] },
  });
  await transcriptIndex.waitUntilComplete();

  const results = await video.semanticSearch("budget discussion", {
    indexIds: [transcriptIndex.indexId],
    topK: 5,
  });
  for (const shot of results.getShots()) {
      console.log(`${shot.start}s: ${shot.text}`);
      await shot.play();
  }
  ```
</CodeGroup>

***

## Storage Control

### Per-Channel Storage

Enable/disable storage for each channel:

<CodeGroup>
  ```python Python theme={null}
  # In desktop client
  channels = await client.list_channels()
  mic = channels.mics.default
  display = channels.displays.default
  system_audio = channels.system_audio.default

  if mic:
      mic.store = True
  if display:
      display.store = True
  if system_audio:
      system_audio.store = False

  selected = [channel for channel in (mic, display, system_audio) if channel]
  await client.start_session(
      capture_session_id=cap_id,
      channels=selected,
  )
  ```

  ```javascript Node.js theme={null}
  // In desktop client
  const channels = await client.listChannels();
  const mic = channels.mics.default;
  const display = channels.displays.default;
  const systemAudio = channels.systemAudio.default;
  const selectedChannels = [
      [mic, true],
      [display, true],
      [systemAudio, false],
  ]
      .filter(([channel]) => Boolean(channel))
      .map(([channel, store]) => ({ channelId: channel.id, type: channel.type, store }));

  await client.startSession({
      sessionId: capId,
      channels: selectedChannels,
  });
  ```
</CodeGroup>

| Setting        | Behavior                                              |
| :------------- | :---------------------------------------------------- |
| `store: true`  | Media persisted, available for search and playback    |
| `store: false` | Ephemeral - real-time processing only, no persistence |

<Note>
  Export only runs if at least one channel has `store: true`.
</Note>

***

## What Gets Exported

### Muxed Video

The default "playable recording" containing:

* **Video:** Primary display (set via `primary_video_channel_id`)
* **Audio:** All recorded audio channels mixed together

**Use for:**

* Playback and sharing
* Downstream indexing and search
* Simple "trim and publish" workflows

### Raw Channel Assets

Individual assets for each stored channel:

| Channel                | Asset Type |
| :--------------------- | :--------- |
| `display:1`            | Raw video  |
| `mic:default`          | Raw audio  |
| `system_audio:default` | Raw audio  |

**Use for:**

* Separate audio stems (mic vs system audio)
* Multi-track editing
* Custom muxing strategies
* Picture-in-picture composites

***

## Accessing Exports

### Via Webhook

The `capture_session.exported` webhook includes the muxed video ID:

```json theme={null}
{
  "event": "capture_session.exported",
  "capture_session_id": "cap-xxx",
  "status": "exported",
  "data": {
    "exported_video_id": "m-xxx"
  }
}
```

### Via RTStream

Each RTStream has an `exported_asset_id` after export:

<Note>
  Mapping Capture channel categories to RTStream IDs is not yet a supported released-SDK contract, so this raw-asset lookup example is deferred.
</Note>

### Using `cap.export()`

You can trigger or check an export programmatically with `cap.export()`:

```python Python theme={null}
cap = conn.get_capture_session("cap-xxx")

result = cap.export(
    video_channel_id=None,      # Optional — defaults to primary video channel
    ws_connection_id=None,       # Optional — for push notification on completion
)
```

| Parameter          | Type            | Description                                                                                                                |
| :----------------- | :-------------- | :------------------------------------------------------------------------------------------------------------------------- |
| `video_channel_id` | `str` or `None` | The video channel to export. Defaults to the primary video channel when omitted.                                           |
| `ws_connection_id` | `str` or `None` | A WebSocket connection ID. When provided, VideoDB sends a push notification over that connection when the export finishes. |

The returned dict contains:

| Field              | Description                                        |
| :----------------- | :------------------------------------------------- |
| `session_id`       | The capture session ID                             |
| `video_channel_id` | The video channel being exported                   |
| `export_status`    | Current status (`exporting`, `exported`, `failed`) |
| `video_id`         | Available once export completes                    |
| `stream_url`       | Playback stream URL (available once exported)      |
| `player_url`       | Embeddable player URL (available once exported)    |

#### Multi-Screen Export

When capturing multiple displays, each display can be exported individually by passing its `video_channel_id`. Use `cap.displays` to discover available video channels:

```python Python theme={null}
cap = conn.get_capture_session("cap-xxx")

for d in cap.displays:
    print(f"{d.channel_id}  primary={d.is_primary}")

# Export a specific (non-primary) display
result = cap.export(video_channel_id="display:2")
print(result)
# {
#   "session_id": "cap-xxx",
#   "video_channel_id": "display:2",
#   "export_status": "exporting"
# }
```

#### Checking Export Status

You can track export completion via webhook or by polling:

```python Python theme={null}
# Option 1: Webhook — your callback_url receives:
# {
#   "event": "capture_session.exported",
#   "capture_session_id": "cap-xxx",
#   "data": { "exported_video_id": "m-xxx" }
# }

# Option 2: Push notification via WebSocket
result = cap.export(ws_connection_id="ws-conn-xxx")

# Option 3: Poll export status
result = cap.export(video_channel_id="display:1")
while result["export_status"] == "exporting":
    time.sleep(5)
    result = cap.export(video_channel_id="display:1")

print(result["video_id"])       # "m-xxx"
print(result["stream_url"])     # Playback URL
print(result["player_url"])     # Embeddable player URL
```

***

## Editing with Raw Assets

Use raw assets when you need control over individual tracks.

### Display Video + Mic Audio Only

<Note>
  This raw-asset composition example is deferred until Capture exposes a supported category-to-RTStream mapping.
</Note>

***

## Semantic Search

After export, captured content is searchable:

<CodeGroup>
  ```python Python theme={null}
  video = coll.get_video(exported_video_id)

  understanding = video.understand(
      analyzers=[{"type": "spoken_words", "name": "transcript"}],
  )
  understanding.wait_until_complete()
  transcript_analyzer = understanding.get_analyzer("transcript")
  transcript_index = video.index(
      name="transcript",
      source=transcript_analyzer,
      use_for=["semantic"],
      fields={"semantic": ["text"]},
  )
  transcript_index.wait_until_complete()

  results = video.semantic_search(
      query="action items from the meeting",
      index_ids=[transcript_index.index_id],
      top_k=5,
  )
  for shot in results.get_shots():
      print(f"{shot.start}s: {shot.text}")
      shot.play()
  ```

  ```javascript Node.js theme={null}
  const video = await coll.getVideo(exportedVideoId);

  const understanding = await video.understand([
    { type: "spoken_words", name: "transcript" },
  ]);
  await understanding.waitUntilComplete();
  const transcriptAnalyzer = await understanding.getAnalyzer("transcript");
  const transcriptIndex = await video.index(transcriptAnalyzer, {
    name: "transcript",
    useFor: ["semantic"],
    fields: { semantic: ["text"] },
  });
  await transcriptIndex.waitUntilComplete();

  const results = await video.semanticSearch("action items from the meeting", {
    indexIds: [transcriptIndex.indexId],
    topK: 5,
  });
  for (const shot of results.getShots()) {
      console.log(`${shot.start}s: ${shot.text}`);
      await shot.play();
  }
  ```
</CodeGroup>

***

## Which Asset to Use?

| Use Case                     | Asset                             |
| :--------------------------- | :-------------------------------- |
| Quick playback, sharing      | Muxed video (`exported_video_id`) |
| Separate mic vs system audio | Raw channel assets                |
| Multi-track editing          | Raw channel assets                |
| Custom audio mix             | Raw channel assets                |
| Picture-in-picture           | Raw channel assets                |

***

## Next Steps

<CardGroup cols={2}>
  <Card icon="lock" title="Privacy Controls" href="/pages/ingest/capture-sdks/privacy-controls">
    Consent and redaction patterns
  </Card>

  <Card icon="camera" title="Capture Overview" href="/pages/ingest/capture-sdks/overview">
    Architecture and quickstart
  </Card>
</CardGroup>
