# Recast

> Faça o recast de um vídeo analisado com seu elenco via REST: cote e compre a execução, responda a pontos de decisão, remixe o áudio ou importe um roteiro.

Source: https://nodaro.ai/pt-BR/docs/developers/api/recast

A **API do Recast** regenera um vídeo analisado com o seu próprio elenco. Você faz a cotação da execução, compra o plano dela, renderiza cena por cena e, em uma execução interativa, escolhe o elenco, as imagens fixas das cenas e a música ao longo do caminho. Você também pode escrever um filme como um roteiro em JSON e importá-lo, e assim um recast não precisa de nenhum vídeo de origem. É o mecanismo por trás do [recast.nodaro.ai](https://recast.nodaro.ai).

O Recast só roda no Nodaro Cloud; em instalações self-hosted, as rotas retornam `404`. Em uma instalação self-hosted, decomponha um vídeo com o nó [**Análise de vídeo** (Video Analysis)](https://nodaro.ai/docs/nodes/video/video-analysis) e regenere as cenas dele com o [**Gerar vídeo** (Generate Video)](https://nodaro.ai/docs/nodes/video/generate-video) em um workflow. As rotas recebem um bearer token. Veja [Autenticação](https://nodaro.ai/docs/developers/api/authentication).

## Endpoints
| Método | Caminho | O que faz | Custo |
| --- | --- | --- | --- |
| `POST` | `/v1/recast/estimate` | Faz a cotação de uma execução. | Grátis |
| `POST` | `/v1/recast` | Cria uma execução. Isso compra o plano. | O plano cotado |
| `GET` | `/v1/recast/:id` | Consulta periodicamente uma execução e lê o ponto de decisão pendente. | Grátis |
| `POST` | `/v1/recast/:id/start` | Começa a renderizar uma execução `planned`. | Coberto pelo plano |
| `POST` | `/v1/recast/:id/select` | Responde a um ponto de decisão pendente. | Grátis |
| `POST` | `/v1/recast/:id/estimate-rescore` | Faz a cotação de uma nova trilha sonora ou de uma nova mixagem. | Grátis |
| `POST` | `/v1/recast/:id/rescore` | Aplica a mudança de áudio cotada. | O preço cotado |
| `GET` | `/v1/video-analysis/authoring-skill` | Retorna o guia para escrever um roteiro. | Grátis |
| `POST` | `/v1/video-analysis/import/validate` | Valida um roteiro. | Grátis |
| `POST` | `/v1/video-analysis/import` | Importa um roteiro como uma análise concluída. | Grátis |

## Cotar e criar uma execução
Uma execução parte de um job de análise: um vídeo analisado pelo nó [Análise de vídeo](https://nodaro.ai/docs/nodes/video/video-analysis) ou um [roteiro importado](#import-a-script-as-a-movie). Primeiro, faça a cotação. `POST /v1/recast/estimate` recebe as configurações com que você vai criar a execução e retorna `{ totalCredits, breakdown }`.

Depois, `POST /v1/recast` cria a execução e compra o plano dela. A rota retorna `{ recastId }`. O corpo precisa de `workflowId`, o ID de um workflow seu ao qual a execução fica vinculada. Sem ele, a rota retorna `400 workflow_id_required`; com um ID desconhecido ou de outra pessoa, `404 workflow_not_found`.

**curl**

```bash
curl -X POST https://app.nodaro.ai/v1/recast/estimate \
  -H "Authorization: Bearer $NODARO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "analysisJobId": "6c1e8a3f-9b2d-4f5a-8e7c-3d1b9a5f2e6c", "resolution": "720p", "interactive": true }'

curl -X POST https://app.nodaro.ai/v1/recast \
  -H "Authorization: Bearer $NODARO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
"workflowId": "8d3f5b7a-1c9e-4a2d-b6f8-4e2a7c9d1b3f",
"analysisJobId": "6c1e8a3f-9b2d-4f5a-8e7c-3d1b9a5f2e6c",
"resolution": "720p",
"interactive": true,
"clientCapabilities": ["sheet-gate"]
}'
```

**TypeScript SDK**

```ts

const client = createClient({
baseUrl: 'https://app.nodaro.ai',
auth: new StaticTokenAuth(process.env.NODARO_API_KEY!),
})

const quote = await client.recast.estimate({ analysisJobId, resolution: '720p', interactive: true })
console.log(quote.totalCredits, quote.breakdown)

const { recastId } = await client.recast.create({
workflowId,
analysisJobId,
resolution: '720p',
interactive: true,
clientCapabilities: ['sheet-gate'],
})
```

**CLI**

```bash
nodaro recast estimate --analysis-job <jobId> --resolution 720p --json
nodaro recast create --workflow <workflowId> --analysis-job <jobId> --resolution 720p --json
```

<TypeTable
type={{
workflowId: { type: 'string (uuid)', description: 'Só na criação. Um workflow seu ao qual a execução fica vinculada.', required: true },
analysisJobId: { type: 'string (uuid)', description: 'A análise da qual fazer o recast: de Análise de vídeo ou de uma importação de roteiro.', required: true },
fidelity: { type: 'string', description: 'O quanto a execução segue a análise. Um roteiro importado usa faithful: exatamente como foi escrito.' },
rightsAttested: { type: 'boolean', description: 'Só na criação. Obrigatório como true para uma renderização faithful de um roteiro importado.' },
resolution: { type: 'string', description: 'A resolução da renderização, por exemplo 480p, 720p ou 1080p.' },
segmentSec: { type: 'string', description: 'Como a renderização agrupa as cenas em partes. É o nome de um agrupamento, não um número de segundos: scenes-max (Longos, com o menor número de emendas), scenes (Curtos) ou max (as partes mais longas que o modelo permite, sem agrupar por cena).' },
renderMethod: { type: 'string', description: 'Como os segmentos são renderizados, por exemplo extend ou keyframes.' },
provider: { type: 'string', description: 'O modelo de vídeo.' },
interactive: { type: 'boolean', description: 'Faz a execução parar nos pontos de decisão para você escolher o elenco, as imagens fixas e a música. Adiciona uma sobretaxa, que a cotação já inclui.' },
clientCapabilities: { type: 'string[]', description: 'Só na criação. Os tipos de ponto de decisão que o seu cliente consegue responder, por exemplo sheet-gate.' },
}}
/>

Para reutilizar um conjunto de configurações de renderização, salve-o como uma predefinição `recast-render`. Veja [Predefinições](https://nodaro.ai/docs/developers/api/presets#recast-render-presets).

## Acompanhar uma execução
`GET /v1/recast/:id` retorna `{ status, interactive?, capabilities?, audio? }`. O status passa por `planning`, `planned`, `generating` e depois `completed` ou `failed`. Uma execução `planned` espera `POST /v1/recast/:id/start`, que começa a renderização e retorna `{ gvpJobId? }`. A rota de início é idempotente e não custa nada a mais, porque a cotação do plano já cobriu a renderização.

**curl**

```bash
curl https://app.nodaro.ai/v1/recast/2e9b4d6f-8a1c-4e3b-9f5d-7c2a4e6b8d1f \
  -H "Authorization: Bearer $NODARO_API_KEY"

curl -X POST https://app.nodaro.ai/v1/recast/2e9b4d6f-8a1c-4e3b-9f5d-7c2a4e6b8d1f/start \
  -H "Authorization: Bearer $NODARO_API_KEY"
```

**TypeScript SDK**

```ts
const run = await client.recast.get(recastId)
if (run.status === 'planned') await client.recast.start(recastId)
```

**CLI**

```bash
nodaro recast status <recastId> --json
nodaro recast start <recastId>
```

## Responder aos pontos de decisão interativos
Uma execução interativa é conduzida pelo servidor: o Nodaro avança cada passo que não exige escolha, e você só consulta o status periodicamente e responde aos pontos de decisão. Quando um ponto de decisão está aguardando, `interactive.next` no status indica qual é. Os pontos de decisão abrem nesta ordem:

| `gate` | O que você escolhe |
| --- | --- |
| `cast` | Um retrato para cada integrante do elenco. |
| `sheet` | Só para uma pessoa, quando a execução oferece: uma de 3 fichas de identidade que compartilham o rosto escolhido, para que você escolha o corpo e o figurino. |
| `anchors` | As imagens fixas de um segmento de cena. |
| `music` | A música de uma seção do filme. |

Um ponto de decisão só abre para os tipos que a sua criação declarou em `clientCapabilities`, por exemplo `sheet-gate`. Qualquer outro ponto de decisão é decidido automaticamente, então um cliente nunca vê uma pergunta que não consegue responder.

Responda com `POST /v1/recast/:id/select`. A escolha é gratuita.

| Campo | O que faz |
| --- | --- |
| `gate` | `cast`, `sheet`, `anchors` ou `music`. |
| `picks` | Para `cast` e `sheet`: as suas escolhas, no formato que o ponto de decisão pendente mostra. |
| `segment`, `anchorPicks` | Para `anchors`: o segmento e `{ start?, end? }`, as imagens fixas escolhidas. |
| `section`, `musicPick` | Para `music`: a seção e a faixa escolhida. |
| `finishAuto` | `true` entrega este ponto de decisão e todos os restantes ao revisor automático. |

```ts
await client.recast.resolveGate(recastId, { gate: 'cast', picks })
await client.recast.resolveGate(recastId, { gate: 'music', section: 0, musicPick: 1, finishAuto: true })
```

Uma execução interativa abandonada não causa problema: ela espera e depois se resolve sozinha quando o prazo dela passa.

## Mudar a trilha sonora ou a mixagem
Depois que um take termina, você pode substituir a música dele ou reequilibrar a mixagem sem renderizar o vídeo de novo. Isso só funciona quando o status traz `capabilities.audioLayers: 1` e o take tem um manifesto `audio`:

```ts
interface RecastAudioManifestV1 {
version: 1
revision: string
mode: 'bed' | 'replace'
present: { music?: true; video?: true }
layers: { music?: { url: string }; video?: { url: string } }
bakedEffectiveGain: { music?: number; video?: number }
pendingRescore?: {
jobId: string
requestId: string
state: 'pending' | 'running'
expectedAudioRevision: string
requestedEffectiveGain: { music?: number; video?: number }
}
}
```

- `present` lista as camadas de áudio que o take tem: `music` e, no modo `bed`, `video`, o som original.
- `layers` lista só as camadas que têm um arquivo de prévia que o seu navegador consegue tocar. Uma camada ausente de `layers` ainda pode estar no download.
- `bakedEffectiveGain` é o nível de cada camada no arquivo atual, em porcentagem.
- `resultUrl`, no status, é a única URL de vídeo que você recebe.

### Cotar e depois aplicar
A cotação e a aplicação recebem a mesma operação. Envie no máximo uma substituição de música, `audioUrl` ou uma ou mais `sections` com um `brief`, mais a `mix` completa que você quer. Uma mixagem sozinha também é válida.

```json
{
"expectedAudioRevision": "server-revision",
"sections": [{ "index": 0, "brief": "Sparse analogue pulse" }],
"mix": {
"music": { "gain": 60, "muted": false },
"video": { "gain": 85, "muted": false }
}
}
```

1. **Cotar.** `POST /v1/recast/:id/estimate-rescore` é gratuito e retorna `{ credits, audioRevision, noOp }`. Ele retorna o preço mesmo quando o seu saldo é insuficiente.
2. **Aplicar.** `POST /v1/recast/:id/rescore` recebe o mesmo corpo mais um `requestId` (um UUID) e o mesmo `expectedAudioRevision`. Ele retorna `{ recastId, jobId }`, ou `{ recastId, noOp: true, audioRevision }` quando nada muda. Uma operação sem efeito não reserva créditos e não cria job.
3. **Acompanhar.** Consulte o status periodicamente. `audio.pendingRescore` mostra a operação, continua lá depois de recarregar a página e desaparece quando a nova revisão é publicada ou a operação falha. Leia o status de novo antes da próxima operação.

Os ganhos são porcentagens de 0 a 200; uma camada silenciada conta como 0. Indique só as camadas de `present` ou a música que esta requisição adiciona. Um take no modo `replace` não tem a camada `video`, e o resultado não pode deixar todas as camadas em silêncio. Reutilize um `requestId` só para repetir exatamente a mesma requisição.

Envie a `mix` completa junto com uma substituição de música. Omiti-la só funciona quando o resultado corresponde aos níveis padrão fixos: música 35 e vídeo 100 no modo `bed`, ou música 100 no modo `replace`. Qualquer outro nível atual retorna `409 legacy_mix_mismatch`.

**curl**

```bash
curl -X POST https://app.nodaro.ai/v1/recast/2e9b4d6f-8a1c-4e3b-9f5d-7c2a4e6b8d1f/rescore \
  -H "Authorization: Bearer $NODARO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
"requestId": "4f6a8c1e-3b5d-4e7f-9a2c-6d8b1f3e5a7c",
"expectedAudioRevision": "server-revision",
"mix": { "music": { "gain": 60, "muted": false }, "video": { "gain": 85, "muted": false } }
}'
```

**TypeScript SDK**

```ts
const status = await client.recast.get(recastId)
const revision = status.audio?.revision
if (status.capabilities?.audioLayers === 1 && revision) {
const operation = {
expectedAudioRevision: revision,
mix: { music: { gain: 60, muted: false }, video: { gain: 85, muted: false } },
}
const quote = await client.recast.estimateRescore(recastId, operation)
if (!quote.noOp) {
await client.recast.rescore(recastId, { ...operation, requestId: crypto.randomUUID() })
}
}
```

## Importar um roteiro como filme
Você pode escrever um filme como um documento JSON, muitas vezes com a ajuda de um modelo de linguagem, e fazer o recast dele sem vídeo de origem. As três rotas são gratuitas.

### Ler o guia de criação
`GET /v1/video-analysis/authoring-skill` retorna o guia em Markdown: os campos do documento, os valores permitidos, os limites, as regras de áudio e um exemplo validado. Entregue o guia ao modelo que escreve o seu roteiro.

### Validar até o roteiro ficar válido
`POST /v1/video-analysis/import/validate` com `{ "script": { … } }` retorna `{ valid, errors, warnings }`. Cada erro tem um `path`, uma `message` e, geralmente, uma `hint` escrita para um ciclo de correção. Corrija cada caminho e valide de novo até `valid` ser `true`.

### Importar o roteiro
`POST /v1/video-analysis/import` com `{ "script": { … }, "rightsAttested": true }` guarda o roteiro como uma análise concluída e retorna `{ jobId, created, warnings, json }`. `json` é o seu documento com os campos que o servidor deriva; guarde-o como a versão oficial do documento. Importar o mesmo roteiro de novo retorna o mesmo `jobId` com `created: false`.

### Fazer o recast
Crie uma execução com esse `jobId` como `analysisJobId`, `fidelity: "faithful"` e `rightsAttested: true`.

`rightsAttested: true` é obrigatório: um recast de roteiro próprio é renderizado exatamente como foi escrito, inclusive com nomes de marcas, então o campo confirma que o roteiro é obra sua. Sem ele, a importação retorna `403 rights_attestation_required`.

O documento tem estas partes:

| Parte | O que guarda |
| --- | --- |
| `meta` | `durationSec`, `width`, `height`, `aspectRatio` (`16:9` ou `9:16`, de acordo com a largura e a altura) e um `title` obrigatório, que dá nome ao projeto. |
| `look` | Opcional. O visual geral do filme. |
| `slots` | O elenco e os cenários, cada um com um `role`: `person`, `object` ou `background`. |
| `scenes` | As cenas, numeradas a partir de 0, sem lacunas, cada uma com 8 segundos ou menos. O total vai de 4 segundos até o limite de execução da plataforma. |

Um documento acima do limite de execução é recusado, nunca encurtado. Não escreva `sceneNumber`, `slotRefs` nem `visualResolved`: o servidor deriva esses campos e ignora os seus valores. É também por isso que uma análise que você copiou do editor com **Copiar JSON** é importada como está.

**curl**

```bash
curl https://app.nodaro.ai/v1/video-analysis/authoring-skill \
  -H "Authorization: Bearer $NODARO_API_KEY" > recast-authoring.md

curl -X POST https://app.nodaro.ai/v1/video-analysis/import \
  -H "Authorization: Bearer $NODARO_API_KEY" \
  -H "Content-Type: application/json" \
  -d "{ \"script\": $(cat script.json), \"rightsAttested\": true }"
```

**TypeScript SDK**

```ts
const guide = await client.recast.authoringSkill()
const check = await client.recast.validateScript(script)
if (check.valid) {
const { jobId } = await client.recast.importScript(script, { rightsAttested: true })
}
```

**CLI**

```bash
nodaro recast skill > recast-authoring.md
nodaro recast validate --file script.json
nodaro recast import --file script.json --rights-attested --json
```

## Usar pelo MCP
Os assistentes de IA fazem o mesmo ciclo com `get_recast_authoring_skill`, `validate_recast_script`, `import_recast_script`, `start_recast`, `get_recast_status` e `resolve_recast_gate`. `start_recast` mostra o preço primeiro e só gasta quando é chamada de novo para confirmar. Veja [Recast pelo MCP](https://nodaro.ai/docs/mcp/recast).

## Erros
| Status | Código | Significado |
| --- | --- | --- |
| `400` | `workflow_id_required` | `POST /v1/recast` foi enviado sem `workflowId`. |
| `400` | `validation_error`, `duplicate_section`, `unknown_section`, `all_audio_silent` | A requisição ou a operação de áudio é inválida. |
| `402` | `insufficient_credits` | A conta não tem créditos para cobrir o plano ou a mudança de áudio. |
| `403` | `rights_attestation_required` | Uma importação de roteiro chegou sem `rightsAttested: true`. |
| `404` | `workflow_not_found` | O workflow não existe ou não é seu. |
| `404` | `not_found` | A execução não existe, ou a instância é self-hosted. |
| `409` | `audio_layers_unavailable`, `audio_layer_unavailable`, `audio_preview_unavailable` | O take não tem áudio com revisões, ou a camada que você indicou está ausente ou não tem uma prévia utilizável. |
| `409` | `rescore_sections_unavailable`, `legacy_mix_mismatch` | Não é possível substituir seções de música neste take, ou uma substituição sem `mix` não corresponde aos níveis atuais. |
| `409` | `stale_audio_revision`, `rescore_in_progress`, `idempotency_conflict` | O áudio mudou, outra mudança está em andamento ou um `requestId` foi reutilizado para uma requisição diferente. Leia o status e tente de novo. |

## Frequently asked questions

### O que é um recast?

Um recast regenera um vídeo analisado, cena por cena, com o seu próprio elenco. A origem pode ser um vídeo real que o Nodaro analisou ou um roteiro que você escreveu em JSON e importou. É o mecanismo por trás do recast.nodaro.ai.

### Como sei quanto um recast vai custar antes de pagar?

Chame POST /v1/recast/estimate com as mesmas configurações com que você vai criar a execução. A rota retorna o total em créditos e um detalhamento, e é gratuita. Depois, POST /v1/recast compra o plano.

### Posso fazer um filme sem um vídeo de origem?

Sim. Escreva o filme como um roteiro em JSON, valide-o de graça e importe-o. A importação cria uma análise da qual você faz o recast com fidelity faithful, para que cada cena seja renderizada exatamente como foi escrita.

### Por que a minha execução interativa nunca para no ponto de decisão sheet?

Um ponto de decisão só abre quando a requisição de criação declarou, em clientCapabilities, que o seu cliente consegue respondê-lo. Os tipos de ponto de decisão não declarados são decididos automaticamente.

### O Recast funciona em uma instalação self-hosted?

Não. As rotas do Recast só rodam no Nodaro Cloud e retornam 404 em instalações self-hosted.
