Docs do Nodaro
DocumentaçãoReferência de nósModelosAgentes de IA (MCP)DesenvolvedoresSelf-hostingPesquisa
API REST

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.

Disponível em Nodaro Cloud

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.

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) e regenere as cenas dele com o Gerar vídeo (Generate Video) em um workflow. As rotas recebem um bearer token. Veja Autenticação.

Endpoints

MétodoCaminhoO que fazCusto
POST/v1/recast/estimateFaz a cotação de uma execução.Grátis
POST/v1/recastCria uma execução. Isso compra o plano.O plano cotado
GET/v1/recast/:idConsulta periodicamente uma execução e lê o ponto de decisão pendente.Grátis
POST/v1/recast/:id/startComeça a renderizar uma execução planned.Coberto pelo plano
POST/v1/recast/:id/selectResponde a um ponto de decisão pendente.Grátis
POST/v1/recast/:id/estimate-rescoreFaz a cotação de uma nova trilha sonora ou de uma nova mixagem.Grátis
POST/v1/recast/:id/rescoreAplica a mudança de áudio cotada.O preço cotado
GET/v1/video-analysis/authoring-skillRetorna o guia para escrever um roteiro.Grátis
POST/v1/video-analysis/import/validateValida um roteiro.Grátis
POST/v1/video-analysis/importImporta 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 ou um roteiro importado. 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 -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"]
  }'
import { createClient, StaticTokenAuth } from '@nodaro/sdk'

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'],
})
nodaro recast estimate --analysis-job <jobId> --resolution 720p --json
nodaro recast create --workflow <workflowId> --analysis-job <jobId> --resolution 720p --json

Prop

Type

Para reutilizar um conjunto de configurações de renderização, salve-o como uma predefinição recast-render. Veja Predefinições.

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 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"
const run = await client.recast.get(recastId)
if (run.status === 'planned') await client.recast.start(recastId)
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:

gateO que você escolhe
castUm retrato para cada integrante do elenco.
sheetSó 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.
anchorsAs imagens fixas de um segmento de cena.
musicA 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.

CampoO que faz
gatecast, sheet, anchors ou music.
picksPara cast e sheet: as suas escolhas, no formato que o ponto de decisão pendente mostra.
segment, anchorPicksPara anchors: o segmento e { start?, end? }, as imagens fixas escolhidas.
section, musicPickPara music: a seção e a faixa escolhida.
finishAutotrue entrega este ponto de decisão e todos os restantes ao revisor automático.
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:

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.

{
  "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 -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 } }
  }'
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:

ParteO que guarda
metadurationSec, 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.
lookOpcional. O visual geral do filme.
slotsO elenco e os cenários, cada um com um role: person, object ou background.
scenesAs 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 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 }"
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 })
}
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.

Erros

StatusCódigoSignificado
400workflow_id_requiredPOST /v1/recast foi enviado sem workflowId.
400validation_error, duplicate_section, unknown_section, all_audio_silentA requisição ou a operação de áudio é inválida.
402insufficient_creditsA conta não tem créditos para cobrir o plano ou a mudança de áudio.
403rights_attestation_requiredUma importação de roteiro chegou sem rightsAttested: true.
404workflow_not_foundO workflow não existe ou não é seu.
404not_foundA execução não existe, ou a instância é self-hosted.
409audio_layers_unavailable, audio_layer_unavailable, audio_preview_unavailableO 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.
409rescore_sections_unavailable, legacy_mix_mismatchNão é possível substituir seções de música neste take, ou uma substituição sem mix não corresponde aos níveis atuais.
409stale_audio_revision, rescore_in_progress, idempotency_conflictO á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.

Perguntas frequentes

Última atualização

Nesta página