> ## 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.

> ## Agent Instructions
> To build and serve your own model, start at /deploy/development/quickstart and /deploy/development/overview. Deploying is the default path: reactor init scaffolds a workspace, reactor auth login authenticates, and reactor model deploy registers the model, publishes the release with the weights/ folder, and activates it on Reactor's GPUs, in one command from that workspace. Docker must be running, because the publish step builds the image locally. Bump model.version in reactor.yaml before redeploying a change, because a release that already has an image is reactivated as it is. Deployment access is granted per account, so contact team@reactor.inc if a deploy is refused. Every key in reactor.yaml is documented at /deploy/platform/reactor-yaml. Model code imports reactor_runtime; Python client code imports reactor_sdk. The runtime overview explains the model interface. Running the model on your own machine with reactor run is optional and needs a GPU you attach with --gpus; /deploy/development/local-testing covers that loop and pairs a complete brightness model with a Python client test in a separate brightness-test workspace.
> Reactor hosts multiple models, each with its own connect slug (modelName) and command/event schema. The catalog of every model — slug, typed SDK package, and links to its schema — is at /model-api-reference/overview. Some models expose one slug per experience (e.g. HappyOyster); always take the slug from the model's own pages, never guess it.
> Fastest path to a working app: `npx create-reactor-app my-app --model=<slug>` scaffolds a complete app with secure auth wired up. Typed TypeScript SDKs are published as @reactor-models/<model>; Python uses the base reactor-sdk package.
> Auth: exchange an API key (rk_...) for a JWT via POST https://api.reactor.inc/tokens from your server. Never put the API key in client-side code.
> Append .md to any docs URL for clean Markdown. Search these docs via the MCP server at https://docs.reactor.inc/mcp.

# Vidu S2-Editing overview

> Edit live webcam video with a reference image and switch looks during a session.

export const ModelRate = ({model, fallback = "View pricing"}) => {
  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 rate = data?.models?.find(m => m.name === model)?.rate;
  const amountPerMin = typeof rate?.amount_per_min === "number" ? rate.amount_per_min : typeof rate?.amount_per_sec === "number" ? rate.amount_per_sec * 60 : undefined;
  const canRender = !error && creditsPerDollar && typeof amountPerMin === "number";
  if (!canRender) {
    return fallback === "View pricing" ? <a href="/resources/billing">View pricing</a> : <span className="text-zinc-950/60 dark:text-white/60">{fallback}</span>;
  }
  const perMin = (amountPerMin / creditsPerDollar).toFixed(3);
  return <span>
      <strong>${perMin}/min</strong>
    </span>;
};

**Vidu S2-Editing** edits your live webcam video with one reference image. Choose an **edit type**
to tell the model what to take from the image: its visual style, a garment, a character, or a
background. Publish your webcam and start an edit. The model returns edited video in real time.

While the edit runs, you can change the image, the edit type, or both, without restarting it. When
you end the edit, the session stays open, so you can start another one.

The [schema](/model-api-reference/vidu-s2-editing/schema) lists the model's commands and messages.
The [prompt guide](/model-api-reference/vidu-s2-editing/prompt-guide) helps you choose images for
each edit type. The [tutorial](/model-api-reference/vidu-s2-editing/tutorial) builds a browser app.

The base wire protocol is the same as every other Reactor model: open a session with the
[`Reactor`](/sdk-reference/reactor-class) class, send named commands, and receive messages. The
model name is **`reactor/vidu-s2-editing`**. The examples use the base SDK.

<Note>
  Usage is billed per session-minute while the session is `ready`, including time between edits.
  Disconnect when you are done. See [Billing](/resources/billing) for how billing maps to rates.
</Note>

## At a glance

| Spec | Value |
| - | - |
| **Model name** | `reactor/vidu-s2-editing` |
| **Pricing** | <ModelRate model="vidu-s2-editing" fallback="Pricing pending" /> |
| **Input** | Webcam video, one reference image, and an edit type |
| **Output** | Video |
| **Output aspect ratio** | 7:4 (952 × 544) in current edited output. Check the received track's dimensions at runtime. |

## Key features

<CardGroup cols={3}>
  <Card title="Live camera in, edited video out" icon="camera">
    Publish your camera and watch the edit in real time. There is nothing to render ahead of time.
  </Card>

  <Card title="Four edit types" icon="layers">
    Restyle the whole scene, try on a garment, replace yourself with a character, or replace the
    background, all from one reference image.
  </Card>

  <Card title="Switch mid-edit" icon="repeat">
    Change the reference image, the edit type, or both with `switch_reference`. The picture changes
    within a few seconds and the edit keeps running.
  </Card>
</CardGroup>

## Quick start

Connect with the base SDK, publish your camera, and start an edit:

<CodeGroup>
  ```typescript JavaScript theme={"theme":{"light":"github-light","dark":"github-dark-high-contrast"}}
  import { Reactor } from "@reactor-team/js-sdk";

  async function startEditing(jwtToken, file, video) {
    const reactor = new Reactor({
      modelName: "reactor/vidu-s2-editing",
      modelTracks: [
        { name: "camera", kind: "video", direction: "sendonly" },
        { name: "main_video", kind: "video", direction: "recvonly" },
      ],
    });

    let previousPhase;
    reactor.on("message", (msg) => {
      if (msg.type === "session_state") {
        console.log(msg.data.phase);
        if (msg.data.phase === "live" && previousPhase !== "live") {
          void reactor.resumeTrack("main_video").catch(console.error);
        }
        previousPhase = msg.data.phase;
      }
      if (msg.type === "command_error") console.error(msg.data.code, msg.data.reason);
    });
    reactor.on("trackReceived", (name, track) => {
      if (name === "main_video") video.srcObject = new MediaStream([track]);
    });

    const initialState = new Promise((resolve) => {
      const onInitialState = (msg) => {
        if (msg.type !== "session_state") return;
        // Stop this temporary listener after the first snapshot.
        reactor.off("message", onInitialState);
        resolve(msg.data);
      };
      reactor.on("message", onInitialState);
    });
    await reactor.connect(jwtToken);
    await initialState;
    await reactor.resumeTrack("main_video");

    // Publish the camera first: nothing is edited until it arrives.
    const stream = await navigator.mediaDevices.getUserMedia({ video: true });
    await reactor.publishTrack("camera", stream.getVideoTracks()[0]);

    const reference_image = await reactor.uploadFile(file);
    await reactor.sendCommand("start_edit", {
      reference_image,
      editing_type: "background_replacement",
    });
    return reactor;
  }
  ```

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

  async with Reactor(model_name="reactor/vidu-s2-editing", api_key="rk_...") as reactor:

      @reactor.on_message
      def on_message(msg):
          if msg["type"] == "session_state":
              print(msg["data"]["phase"], msg["data"]["camera_forwarding"])

      await reactor.connect()

      # Publish the camera slot, then push frames into it (see Tracks).
      camera = await reactor.track("camera").publish()

      await reactor.send_command("start_edit", {
          "reference_image_url": "https://example.com/beach.jpg",
          "editing_type": "background_replacement",
      })
  ```
</CodeGroup>

See [Authentication](/authentication) to connect your app, [Tracks](/concepts/tracks) to publish a
camera from each SDK, and the [tutorial](/model-api-reference/vidu-s2-editing/tutorial) for a full
browser app.

## How it works

1. **Connect.** The first `session_state` message reports phase `idle`. The `camera` and
   `main_video` tracks exist from the start, so you can attach a player and publish your camera
   right away. Until an edit is running, `main_video` shows a neutral dark frame.
2. **Publish your camera.** The model edits nothing until your camera reaches it. Publish `camera`
   before or right after `start_edit`.
3. **Start an edit.** Call `start_edit` with a reference image and an edit type. The phase moves
   through `starting` and `warming_up` to `live`. `camera_forwarding` turns `true` once your camera
   reaches the model. `video_receiving` turns `true` when edited frames arrive. Use these fields to
   show progress. The first edited frame can arrive after the phase becomes `live`.
4. **Switch.** Call `switch_reference` to change the image, the edit type, or both. The picture
   changes within a few seconds.
5. **End the edit.** Call `end_edit`, or let the edit end at its time limit. The session stays open,
   so `start_edit` works again.
