# Recast

> Cote, compre e acompanhe execuções do Recast em TypeScript, responda aos pontos de decisão, importe um roteiro próprio e mude a mixagem de um recast pronto.

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

**`client.recast`** executa o Recast: ele gera de novo um vídeo de origem já analisado com o seu próprio elenco e também pode renderizar um roteiro que você escreveu, um “movie as JSON”. Você faz a cotação de uma execução, compra, acompanha e responde aos pontos de decisão dela de elenco, folhas, quadros-âncora e música. Os métodos chamam a [API REST do Recast](https://nodaro.ai/docs/developers/api/recast). Veja [Recast pelo MCP](https://nodaro.ai/docs/mcp/recast) para o guia de criação.

O Recast é executado no Nodaro Cloud. Em uma instalação self-hosted, estes métodos lançam `NotFoundError`.

## Métodos
| Método | O que faz |
| --- | --- |
| [`authoringSkill()`](#authoringskill) | Lê o guia de criação de roteiros |
| [`validateScript(script)`](#validatescriptscript) | Verifica um roteiro próprio |
| [`importScript(script, opts)`](#importscriptscript-opts) | Armazena um roteiro próprio como uma análise |
| [`estimate(input)`](#estimateinput) | Faz a cotação de uma execução, de graça |
| [`create(input)`](#createinput) | Compra o plano e cria a execução |
| [`get(recastId)`](#getrecastid) | Lê o status da execução e o ponto de decisão pendente |
| [`start(recastId, opts?)`](#startrecastid-opts) | Começa a renderizar uma execução planejada |
| [`resolveGate(recastId, input)`](#resolvegaterecastid-input) | Responde a um ponto de decisão pendente |
| [`estimateRescore(recastId, input)`](#estimaterescorerecastid-input) | Faz a cotação de uma nova mixagem ou de uma nova faixa de música |
| [`rescore(recastId, input)`](#rescorerecastid-input) | Aplica a alteração de áudio cotada |

## Roteiros próprios
### authoringSkill()
Retorna o guia de criação, em Markdown (`GET /v1/video-analysis/authoring-skill`). Ele cobre o documento do roteiro, os vocabulários e os limites dele, as regras de áudio e um exemplo verificado. É gratuito.

```ts
authoringSkill(): Promise<string>
```

```ts
const guide = await client.recast.authoringSkill()
```

Entregue o guia ao modelo de linguagem que escreve o seu roteiro.

### validateScript(script)
Verifica um roteiro próprio e retorna `{ valid, errors, warnings }` (`POST /v1/video-analysis/import/validate`). É gratuito e não armazena nada.

```ts
validateScript(script: Record<string, unknown>): Promise<{
valid: boolean
errors: Array<{ path: string; message: string; hint?: string }>
warnings: string[]
}>
```

<TypeTable
type={{
script: { type: 'Record<string, unknown>', required: true, description: "O documento do roteiro." },
}}
/>

```ts
let check = await client.recast.validateScript(script)
while (!check.valid) {
script = await fixWithModel(script, check.errors) // each error has a path, a message and usually a hint
check = await client.recast.validateScript(script)
}
```

Cada erro indica o `path` que está errado e normalmente traz uma `hint` escrita para um modelo de linguagem. Corrija e valide de novo até `valid` ser `true`.

### importScript(script, opts)
Armazena um roteiro válido como um job de análise concluído (`POST /v1/video-analysis/import`). Use o `jobId` dele como o `analysisJobId` de um recast. É gratuito.

```ts
importScript(script: Record<string, unknown>, opts: { rightsAttested: true }): Promise<{
jobId: string
created: boolean
warnings: string[]
json: Record<string, unknown>
}>
```

<TypeTable
type={{
script: { type: 'Record<string, unknown>', required: true, description: "Um documento de roteiro válido." },
rightsAttested: { type: 'true', required: true, description: "Você confirma que é dono da obra. Sem isso, a importação falha com um ForbiddenError." },
}}
/>

```ts
const { jobId: analysisJobId, json } = await client.recast.importScript(script, { rightsAttested: true })
```

Um recast de roteiro próprio é renderizado exatamente como foi escrito, então importe só obras que são suas. `json` é o seu documento com os campos que o servidor adiciona; prefira-o à sua entrada. Importar o mesmo roteiro de novo retorna `created: false`.

## A execução
### estimate(input)
Faz a cotação de uma execução em créditos (`POST /v1/recast/estimate`). É gratuito. O corpo é o mesmo de `create()`, sem `workflowId`, `rightsAttested` e `clientCapabilities`.

```ts
estimate(input: EstimateRecastInput): Promise<{ totalCredits?: number; breakdown?: Record<string, number> }>
```

<TypeTable
type={{
analysisJobId: { type: 'string', required: true, description: "A análise de vídeo ou o roteiro importado a usar no recast." },
fidelity: { type: 'string', description: "O quanto a execução segue a origem. Um roteiro próprio usa faithful." },
resolution: { type: 'string', description: "A resolução de saída." },
segmentSec: { type: 'number', description: "Como a renderização agrupa as cenas em partes. O servidor recebe aqui o nome de um pacote: scenes-max (o menor número de emendas), scenes (partes mais curtas) ou max (as partes mais longas que o modelo permite). Veja a nota abaixo." },
renderMethod: { type: 'string', description: "O método de renderização." },
interactive: { type: 'boolean', description: "Faz uma parada em cada ponto de decisão para você escolher." },
provider: { type: 'string', description: "O modelo de vídeo." },
}}
/>

```ts
const quote = await client.recast.estimate({ analysisJobId, fidelity: "faithful" })
console.log(quote.totalCredits, quote.breakdown)
```

**O `segmentSec` no SDK 2.17.0.** As rotas do Recast recebem o nome de um pacote em `segmentSec`: `"scenes-max"`, `"scenes"` ou `"max"`. O SDK 2.17.0 tipa o campo como número, então passe o nome com um cast, como `segmentSec: "scenes" as unknown as number`, ou deixe o campo de fora para usar o padrão do servidor. Um número como `10` é recusado com um 400. O mesmo vale para `create()` e `start()`.

### create(input)
Cria a execução e **compra o plano** (`POST /v1/recast`). Faça a cotação com `estimate()` antes.

```ts
create(input: CreateRecastInput): Promise<{ recastId: string }>
```

<TypeTable
type={{
workflowId: { type: 'string', required: true, description: "Um workflow existente que é seu. A execução fica vinculada a ele." },
analysisJobId: { type: 'string', required: true, description: "A análise de vídeo ou o roteiro importado a usar no recast." },
rightsAttested: { type: 'boolean', description: "Você confirma que tem os direitos sobre a origem." },
clientCapabilities: { type: 'string[]', description: "Os tipos de ponto de decisão que o seu cliente consegue responder, como sheet-gate. Os pontos de decisão que você não declarar são decididos automaticamente." },
'...': { type: 'EstimateRecastInput', description: "Os campos de estimate()." },
}}
/>

```ts
const { recastId } = await client.recast.create({
workflowId,
analysisJobId,
fidelity: "faithful",
rightsAttested: true,
interactive: true,
clientCapabilities: ["sheet-gate"],
})
```

Sem `workflowId`, a chamada falha com `400 workflow_id_required`; um workflow desconhecido ou de outra pessoa falha com um 404.

### get(recastId)
Lê a execução (`GET /v1/recast/:id`). Consulte-a periodicamente para acompanhar o progresso. Em uma execução interativa, `interactive.next` indica o passo ou o ponto de decisão pendente.

```ts
get(recastId: string): Promise<RecastRunSnapshot>
```

<TypeTable
type={{
recastId: { type: 'string', required: true, description: "O ID do recast." },
}}
/>

```ts
const run = await client.recast.get(recastId)
console.log(run.status, run.interactive)
```

O snapshot tem `status`, `interactive`, `capabilities` e, em uma execução concluída com camadas de áudio separadas, `audio`. Veja [Alterar a mixagem da música](#change-the-music-mix).

### start(recastId, opts?)
Começa a renderizar uma execução no estado `planned` (`POST /v1/recast/:id/start`). A cotação do plano já cobre essa renderização, e uma chamada repetida não muda nada.

```ts
start(recastId: string, opts?: { segmentSec?: number; provider?: string }): Promise<{ gvpJobId?: string }>
```

<TypeTable
type={{
recastId: { type: 'string', required: true, description: "O ID do recast." },
segmentSec: { type: 'number', description: "Como a renderização agrupa as cenas em partes: o nome de um pacote, como em estimate()." },
provider: { type: 'string', description: "O modelo de vídeo." },
}}
/>

```ts
const { gvpJobId } = await client.recast.start(recastId)
```

### resolveGate(recastId, input)
Responde a um ponto de decisão pendente em uma execução interativa (`POST /v1/recast/:id/select`). A plataforma avança todos os outros passos sozinha, então um cliente só consulta `get()` periodicamente e responde aos pontos de decisão. A escolha em si é gratuita.

```ts
resolveGate(recastId: string, input: ResolveRecastGateInput): Promise<Record<string, unknown>>
```

<TypeTable
type={{
recastId: { type: 'string', required: true, description: "O ID do recast." },
gate: { type: '"cast" | "sheet" | "anchors" | "music"', description: "O ponto de decisão que você responde." },
picks: { type: 'unknown', description: "A sua opção para um ponto de decisão de elenco ou de folha." },
segment: { type: 'number', description: "O segmento ao qual o ponto de decisão pertence." },
anchorPicks: { type: '{ start?: number; end?: number }', description: "Os seus quadros-âncora para um ponto de decisão de âncoras." },
section: { type: 'number', description: "A seção de música à qual o ponto de decisão pertence." },
musicPick: { type: 'number | string', description: "A sua opção para um ponto de decisão de música." },
finishAuto: { type: 'boolean', description: "Entrega este ponto de decisão e todos os restantes ao crítico automático." },
}}
/>

```ts
await client.recast.resolveGate(recastId, { gate: "music", musicPick: 1 })
await client.recast.resolveGate(recastId, { finishAuto: true })
```

Um ponto de decisão só é aberto para os tipos que o seu `create()` declarou em `clientCapabilities`. A plataforma decide os demais automaticamente.

## Alterar a mixagem da música
Um recast concluído pode manter a música e o som do próprio vídeo como camadas separadas. Quando `get()` retorna `capabilities.audioLayers` definido como `1`, o manifesto `audio` descreve essas camadas:

- **`revision`** identifica o áudio atual. Toda alteração precisa dele.
- **`present`** lista as camadas que o recast tem, `music` e `video`.
- **`layers`** guarda arquivos de prévia das camadas que existem.
- **`bakedEffectiveGain`** descreve os níveis do download atual.
- **`pendingRescore`** descreve uma alteração ainda em andamento. Ele continua lá depois que a página é recarregada.

Nunca invente uma revisão e nunca deduza a mixagem a partir de uma prévia ausente.

### estimateRescore(recastId, input)
Faz a cotação de uma alteração de áudio: uma nova mixagem, uma nova faixa de música ou as duas (`POST /v1/recast/:id/estimate-rescore`). É gratuito e retorna `{ credits, audioRevision, noOp }`.

```ts
estimateRescore(recastId: string, input: EstimateRecastRescoreInput): Promise<{ credits: number; audioRevision: string; noOp: boolean }>
```

<TypeTable
type={{
recastId: { type: 'string', required: true, description: "O ID do recast." },
expectedAudioRevision: { type: 'string', required: true, description: "O audio.revision que você leu." },
mix: { type: '{ music?: { gain, muted }, video?: { gain, muted } }', description: "A mixagem completa que você quer, com um ganho e uma opção de silenciar por camada." },
audioUrl: { type: 'string', description: "Uma nova faixa de música para substituir a música." },
sections: { type: 'Array<{ index: number; brief: string }>', description: "Seções de música a gerar de novo, cada uma com uma breve descrição. Use audioUrl ou sections, não os dois." },
}}
/>

### rescore(recastId, input)
Aplica a alteração de áudio (`POST /v1/recast/:id/rescore`). Envie a mesma operação que você cotou, mais um novo `requestId`, um UUID. Reutilize esse `requestId` só para repetir a mesma requisição depois de uma falha de rede.

```ts
rescore(recastId: string, input: EstimateRecastRescoreInput & { requestId: string }): Promise<
| { recastId: string; jobId: string }
| { recastId: string; noOp: true; audioRevision: string }
>
```

<TypeTable
type={{
recastId: { type: 'string', required: true, description: "O ID do recast." },
requestId: { type: 'string', required: true, description: "Um novo UUID para esta alteração." },
'...': { type: 'EstimateRecastRescoreInput', description: "A mesma operação que você enviou para estimateRescore." },
}}
/>

```ts
const { total: available } = await client.credits.balance()
const run = await client.recast.get(recastId)

if (run.capabilities?.audioLayers === 1 && run.audio) {
const operation = {
expectedAudioRevision: run.audio.revision,
mix: {
music: { gain: 55, muted: false },
video: { gain: 100, muted: false },
},
}
const quote = await client.recast.estimateRescore(recastId, operation)
if (!quote.noOp && available >= quote.credits) {
await client.recast.rescore(recastId, { ...operation, requestId: crypto.randomUUID() })
}
}
```

A alteração é executada como um job. Consulte `get(recastId)` periodicamente até `audio.pendingRescore` desaparecer e `audio.revision` mudar. Uma alteração que não faria nada retorna `noOp: true`, sem job.

- Uma operação contém uma mixagem, uma substituição de música (`audioUrl` ou `sections`), ou uma substituição com a mixagem dela.
- Com uma substituição, envie a mixagem completa que você quer. Deixá-la de fora só funciona para os níveis padrão antigos exatos e, nos outros casos, falha com `409 legacy_mix_mismatch`.
- Um `expectedAudioRevision` desatualizado falha com `409 stale_audio_revision`, e uma segunda alteração enquanto outra está em execução falha com `409 rescore_in_progress`. Leia o recast de novo e faça a cotação de novo.
- Uma operação inválida falha com um 400, como `validation_error`, `unknown_section`, `duplicate_section` ou `all_audio_silent`.
- Uma cotação nunca reserva créditos e retorna o preço mesmo quando o seu saldo é insuficiente.

## Frequently asked questions

### O que o Recast faz?

O Recast gera de novo um vídeo de origem já analisado com o seu próprio elenco. O Nodaro planeja a execução a partir da análise, renderiza segmento por segmento e deixa você escolher o elenco, as folhas, os quadros-âncora e a música ao longo do caminho.

### Criar um recast custa créditos?

Sim. create compra o plano, então faça antes a cotação com client.recast.estimate, que é gratuito. Responder a um ponto de decisão com resolveGate é gratuito.

### Posso fazer o recast de um roteiro meu em vez de um vídeo real?

Sim. Valide o roteiro com validateScript até ele ser válido e depois chame importScript com rightsAttested definido como true. A importação retorna um jobId que você usa como o analysisJobId de um novo recast.

### Como altero o nível da música de um recast pronto?

Leia o recast com get, faça a cotação da nova mixagem com estimateRescore e depois envie a mesma operação para rescore com um novo requestId. Consulte get periodicamente até a revisão do áudio mudar.
