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

# Overview

> What SANA-Streaming is, its key features, and a quick start.

export const ModelRate = ({model}) => {
  const [data, setData] = useState(null);
  const [error, setError] = useState(false);
  const [loading, setLoading] = useState(true);
  useEffect(() => {
    const ctrl = new AbortController();
    fetch("https://api.reactor.inc/pricing", {
      signal: ctrl.signal
    }).then(r => {
      if (!r.ok) throw new Error(`HTTP ${r.status}`);
      return r.json();
    }).then(json => {
      setData(json);
      setLoading(false);
    }).catch(err => {
      if (err.name === "AbortError") return;
      setError(true);
      setLoading(false);
    });
    return () => ctrl.abort();
  }, []);
  if (loading) {
    return <span aria-label="loading pricing" className="inline-block h-4 w-28 rounded bg-zinc-950/10 dark:bg-white/10 animate-pulse align-middle" />;
  }
  const creditsPerDollar = data?.settings?.credits_per_dollar;
  const amountPerSec = data?.models?.find(m => m.name === model)?.rate?.amount_per_sec;
  const canRender = !error && creditsPerDollar && typeof amountPerSec === "number";
  if (!canRender) {
    return <span className="text-zinc-950/60 dark:text-white/60">see current rate below</span>;
  }
  const perHour = Math.round(amountPerSec / creditsPerDollar * 3600);
  const perSec = (amountPerSec / creditsPerDollar).toFixed(4);
  return <span>
      <strong>${perHour}/hr</strong> (${perSec}/sec)
    </span>;
};

**SANA-Streaming** is a real-time **video editing** model. Feed it a video clip or a live webcam
feed and describe a change in plain text. The model outputs an edited video stream alongside the
source footage. The edit lands where you aim it; everything you don't mention carries through from
the source untouched.

SANA-Streaming **edits** the video you bring rather than generating one from scratch. The prompt
describes a change to apply, not a world to create, and the model's job is to make that change
and preserve everything else.

The SANA-Streaming reference is split across four pages: this overview, the complete
[command and message schema](/model-api-reference/sana-streaming/schema), the
[prompt guide](/model-api-reference/sana-streaming/prompt-guide) for writing surgical edit prompts,
and an end-to-end [tutorial](/model-api-reference/sana-streaming/tutorial).

Build on SANA-Streaming with the typed
[`@reactor-models/sana-streaming`](/sdk-reference/typed-model-sdk) SDK: a `<SanaStreamingProvider>`
and hooks for React, a `SanaStreamingModel` class for plain JavaScript, with every command, message,
and track checked at compile time. It wraps the same base wire protocol every Reactor model speaks,
so you can also drive it from the base [`Reactor`](/sdk-reference/reactor-class) client by command
name (the path for Python). SANA-Streaming's surface adds commands for setting the source video and
applying edit prompts.

## At a glance

| Spec           | Value                                |
| -------------- | ------------------------------------ |
| **Model name** | `reactor/sana-streaming`             |
| **Pricing**    | <ModelRate model="sana-streaming" /> |
| **Resolution** | 1280 × 704                           |

The **model name** is `reactor/sana-streaming`. The typed SDK bakes it in
(`new SanaStreamingModel()`, `<SanaStreamingProvider>`); with the base class you pass it yourself
(`new Reactor({ modelName: "reactor/sana-streaming" })`). See
[Pricing & Billing](/resources/billing) for how billing works.

## Key features

<CardGroup cols={3}>
  <Card title="Surgical edits" icon="scissors">
    Change the one thing you name and nothing else.
  </Card>

  <Card title="Real-time responsiveness" icon="zap">
    Send a new edit mid-stream and watch it land about a second later, not after a re-render.
  </Card>

  <Card title="Live webcam input" icon="camera">
    Restyle yourself, your room, or your product demo as it streams.
  </Card>
</CardGroup>

## Quick start

The fastest path to a working SANA-Streaming app is the `create-reactor-app` CLI, which scaffolds a
runnable project for you. You can also clone the
[open-source reference frontend](https://github.com/reactor-team/js-sdk/tree/main/examples/sana-streaming)
directly, or follow the [tutorial](/model-api-reference/sana-streaming/tutorial) for a guided
walkthrough.

<Tabs>
  <Tab title="npm">
    ```shell theme={"theme":{"light":"github-light","dark":"github-dark-high-contrast"}}
    npx create-reactor-app my-sana-app --model=sana-streaming
    ```
  </Tab>

  <Tab title="pnpm">
    ```shell theme={"theme":{"light":"github-light","dark":"github-dark-high-contrast"}}
    pnpm create reactor-app my-sana-app --model=sana-streaming
    ```
  </Tab>
</Tabs>

Working in Python instead? The CLI is JavaScript-only, so install the SDK with
`pip install reactor-sdk` and drive the same edit from the base SDK — see [From Python](#from-python)
below (and the [quickstart](/quickstart) for the full walkthrough).

A minimal connect-and-edit flow in React wraps your app in `<SanaStreamingProvider>` and drives the
edit from a child component with `useSanaStreaming()`:

<Tabs>
  <Tab title="Live webcam">
    ```tsx theme={"theme":{"light":"github-light","dark":"github-dark-high-contrast"}}
    import {
      SanaStreamingProvider,
      useSanaStreaming,
      SanaStreamingMainVideoView,
    } from "@reactor-models/sana-streaming";
    import { useEffect } from "react";

    function Editor() {
      const { status, publish, setPrompt, setMode, start } = useSanaStreaming();

      useEffect(() => {
        if (status !== "ready") return;
        void (async () => {
          // Publish your camera as the live source. Pin the resolution first:
          // a mid-chunk resolution change crashes the session.
          const cam = await navigator.mediaDevices.getUserMedia({ video: true });
          const track = cam.getVideoTracks()[0];
          track.contentHint = "detail";
          await publish("camera", track);

          // Describe the edit, then start.
          await setPrompt({
            prompt:
              "Replace the subject's t-shirt with a vintage dark brown leather aviator jacket with a cream shearling collar.",
          });
          await setMode({ mode: "live" });
          await start();
        })();
      }, [status]);

      // Render the edited stream as frames arrive.
      return <SanaStreamingMainVideoView className="w-full aspect-video" />;
    }

    export default function App() {
      // getToken() mints a short-lived JWT on your server (see Authentication).
      return (
        <SanaStreamingProvider getJwt={getToken} connectOptions={{ autoConnect: true }}>
          <Editor />
        </SanaStreamingProvider>
      );
    }
    ```
  </Tab>

  <Tab title="Uploaded video">
    ```tsx theme={"theme":{"light":"github-light","dark":"github-dark-high-contrast"}}
    import {
      SanaStreamingProvider,
      useSanaStreaming,
      SanaStreamingMainVideoView,
    } from "@reactor-models/sana-streaming";

    function Editor({ file }: { file: File }) {
      const { status, uploadFile, setVideo, setPrompt, setMode, start } = useSanaStreaming();

      // Call once status === "ready", e.g. from a button.
      async function edit() {
        // Upload a clip (at least 33 frames) and set it as the source...
        const ref = await uploadFile(file); // a video File or Blob
        await setVideo({ video: ref });

        // ...describe the edit, then start.
        await setPrompt({
          prompt:
            "Replace the background with a rain-streaked window at dusk, city lights glowing through the wet glass.",
        });
        await setMode({ mode: "file" });
        await start();
      }

      return (
        <>
          <button disabled={status !== "ready"} onClick={edit}>
            Apply edit
          </button>
          {/* Render the edited stream as frames arrive. */}
          <SanaStreamingMainVideoView className="w-full aspect-video" />
        </>
      );
    }

    export default function App() {
      // getToken() mints a short-lived JWT on your server (see Authentication).
      return (
        <SanaStreamingProvider getJwt={getToken} connectOptions={{ autoConnect: true }}>
          {/* Render <Editor file={...} /> once the user picks a clip. */}
        </SanaStreamingProvider>
      );
    }
    ```
  </Tab>
</Tabs>

`publish` and `uploadFile` come from the base SDK and work the same on every model; `useSanaStreaming()`
exposes them next to the typed commands (see [Tracks](/concepts/tracks) and
[File Uploads](/concepts/file-uploads)). To change the edit mid-stream, call `setPrompt` again: the
new edit lands at the next chunk boundary, about a second later. Prefer plain JavaScript?
`new SanaStreamingModel()` carries the same typed methods; see the
[schema's complete example](/model-api-reference/sana-streaming/schema#complete-example).

### From Python

The base SDK drives the same edit from Python. Upload a clip, set it as the source, describe the
edit, then start; edited frames arrive as NumPy arrays. For a live camera source instead, publish a
track with [`publish_track()`](/sdk-reference/python/reactor#publish_track) and use `set_mode` with
`"live"`.

```python theme={"theme":{"light":"github-light","dark":"github-dark-high-contrast"}}
import asyncio
import os
from reactor_sdk import Reactor, ReactorStatus

async def main():
    reactor = Reactor(model_name="reactor/sana-streaming", api_key=os.environ["REACTOR_API_KEY"])

    # Edited frames arrive as (H, W, 3) uint8 RGB NumPy arrays.
    @reactor.on_frame
    def on_frame(frame):
        print(f"Frame: {frame.shape}")

    # Once the session is ready, set a source clip and prompt, then start.
    @reactor.on_status(ReactorStatus.READY)
    async def on_ready(status):
        ref = await reactor.upload_file("clip.mp4")  # at least 33 frames
        await reactor.send_command("set_video", {"video": ref})
        await reactor.send_command("set_prompt", {
            "prompt": "Replace the background with a rain-streaked window at dusk, city lights glowing through the wet glass.",
        })
        # Send set_mode and start as a pair.
        await reactor.send_command("set_mode", {"mode": "file"})
        await reactor.send_command("start", {})

    await reactor.connect()
    await asyncio.Event().wait()  # run until interrupted

asyncio.run(main())
```

## How it works

On connect the model is live but idle. Give it a source (your camera or an uploaded clip) and call
`start`; the edited stream begins on `main_video`. A prompt is optional: with none the model streams
a near-reconstruction of the source, and setting one steers the edit. From there you direct it in
real time: send a new prompt and the change lands at the next chunk boundary, so successive edits
feel like adjustments to a live feed rather than separate render jobs.

The architecture comes from the paper *SANA-Streaming: Real-time Streaming Video Editing with Hybrid
Diffusion Transformer* (Zhao et al., 2026), research led by NVIDIA with collaborators at MIT, THU,
NUS, and HKU. {/* TODO: link the paper title once the arXiv ID is published. */}

See the [schema](/model-api-reference/sana-streaming/schema) for every command and message, and the
[prompt guide](/model-api-reference/sana-streaming/prompt-guide) for how to write edit prompts that
land where you aim them.
