# Edição

> Edite podcasts e vídeos longos em TypeScript. Detecte silêncio, sincronize gravações, planeje cortes a partir de uma transcrição e renderize uma EDL.

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

**`client.edit`** reúne as ferramentas de edição para podcasts e vídeos longos. Ele encontra silêncios, mede o deslocamento de tempo entre várias gravações, planeja um corte a partir de uma transcrição e renderiza uma **lista de decisões de edição** (EDL) em um vídeo ou arquivo de áudio final. Quatro métodos iniciam um job e retornam `{ jobId }`, que você consulta periodicamente com [`client.jobs.getStatus()`](https://nodaro.ai/docs/developers/sdk/jobs-and-executions). Um quinto, `remapTranscript()`, roda localmente, sem requisição.

## Métodos
| Método | Endpoint | O que faz |
| --- | --- | --- |
| [`edit.silenceDetect(input)`](#editsilencedetectinput) | `POST /v1/silence-detect` | Encontra os trechos silenciosos de uma fonte de áudio ou vídeo |
| [`edit.audioSync(input)`](#editaudiosyncinput) | `POST /v1/audio-sync` | Mede o deslocamento de tempo entre gravações, de 2 a 6 |
| [`edit.editPlan(input)`](#editeditplaninput) | `POST /v1/edit-plan` | Planeja um corte mais enxuto, clipes curtos ou capítulos a partir de uma transcrição |
| [`edit.applyEdl(input)`](#editapplyedlinput) | `POST /v1/apply-edl` | Renderiza uma EDL em um vídeo ou um arquivo de áudio |
| [`edit.remapTranscript(edl, transcript)`](#editremaptranscriptedl-transcript) | Nenhum, local | Move os tempos de uma transcrição para a linha do tempo editada |

## Editar um podcast pelo código
### Transcrever a gravação
Execute [`client.audio.transcribe()`](https://nodaro.ai/docs/developers/sdk/voices-and-audio#audiotranscribeinput) com um mecanismo que retorne os tempos por palavra. O `output_data.json` dele é a transcrição de que o plano precisa.

### Encontrar o silêncio
Execute `silenceDetect()` na gravação principal. O `output_data.json` dele contém os trechos silenciosos.

### Planejar o corte
Execute `editPlan()` no modo `tighten` com a transcrição, as fontes e o silêncio. Leia o plano com `unwrapEditPlanOutput()`.

### Renderizar
Passe o plano para `applyEdl()`. O job concluído contém o vídeo ou o áudio editado.

Workflow: A mesma cadeia de edição em nós: transcrever e encontrar o silêncio, planejar um corte mais enxuto e depois renderizá-lo.

- Enviar vídeo → Transcrever
- Enviar vídeo → Detectar silêncio
- Transcrever → Plano de edição
- Detectar silêncio → Plano de edição
- Plano de edição → Aplicar EDL

```ts

async function outputOf(jobId: string): Promise<any> {
for (;;) {
const { data } = await client.jobs.getStatus(jobId)
if (data.status === "completed") return data.output_data
if (data.status === "failed" || data.status === "cancelled") throw new Error(data.error_message ?? data.status)
await new Promise((resolve) => setTimeout(resolve, 3_000))
}
}

// 1. Transcribe with word timings
const tr = await client.audio.transcribe({ audioUrl: masterUrl, provider: "elevenlabs-stt" })
const transcript = (await outputOf(tr.jobId)).json

// 2. Find the silence
const sd = await client.edit.silenceDetect({ audioUrl: masterUrl, thresholdDb: -35 })
const silence = (await outputOf(sd.jobId)).json

// 3. Plan a tighter cut
const plan = await client.edit.editPlan({
mode: "tighten",
planTier: "standard",
transcript,
sources: [{ id: "ep", url: masterUrl, kind: "video", role: "master-audio" }],
silence,
})
const edl = unwrapEditPlanOutput(await outputOf(plan.jobId))

// 4. Render the plan
const render = await client.edit.applyEdl({ edl, output: "video", quality: "final" })
const { videoUrl } = await outputOf(render.jobId)
```

As mesmas etapas existem como nós: [**Transcrever** (Transcribe)](https://nodaro.ai/docs/nodes/audio/transcribe), [**Detectar silêncio** (Silence Detect)](https://nodaro.ai/docs/nodes/audio/silence-detect), [**Plano de edição** (Edit Plan)](https://nodaro.ai/docs/nodes/video/edit-plan) e [**Aplicar EDL** (Apply EDL)](https://nodaro.ai/docs/nodes/video/apply-edl).

## client.edit
### edit.silenceDetect(input)
Encontra os trechos silenciosos de uma fonte de áudio ou vídeo. Roda no servidor, sem modelo de IA.

```ts
silenceDetect(input: SilenceDetectInput): Promise<{ jobId: string }>
```

<TypeTable
type={{
audioUrl: { type: 'string', required: true, description: "Uma fonte de áudio ou vídeo." },
thresholdDb: { type: 'number', default: '-35', description: "O volume abaixo do qual o áudio conta como silêncio, em dBFS, 0 ou menos." },
minSilenceMs: { type: 'number', default: '700', description: "O silêncio mais curto a informar, em milissegundos." },
padMs: { type: 'number', default: '120', description: "A margem mantida em volta da fala, em milissegundos. Ela encolhe cada trecho silencioso." },
workflowId: { type: 'string', description: "Um workflow em cujo histórico de execuções esta execução vai aparecer." },
}}
/>

```ts
const { jobId } = await client.edit.silenceDetect({ audioUrl: masterUrl, minSilenceMs: 900 })
```

O `output_data.json` do job concluído é um objeto `SilenceRanges`: `{ version, ranges: [{ startMs, endMs }], durationMs }`. Passe o objeto inteiro como `silence` para `editPlan()`.

### edit.audioSync(input)
Mede, pelo som, o deslocamento entre os relógios de 2 a 6 gravações de uma mesma conversa, como faz o nó [**Sincronizar áudio** (Audio Sync)](https://nodaro.ai/docs/nodes/audio/audio-sync). Roda no servidor, sem modelo de IA. Custa 11 créditos por fonte depois da primeira: 11 para 2 fontes, 33 para 4 e 55 para 6.

```ts
audioSync(input: AudioSyncInput): Promise<{ jobId: string }>
```

<TypeTable
type={{
sources: { type: 'Array<{ id: string; url: string }>', required: true, description: "De 2 a 6 gravações. Cada id, com 1 a 200 caracteres e único, volta como o sourceId do deslocamento correspondente. Use os ids que a sua EDL usa para as mesmas gravações." },
reference: { type: 'string', description: "O id da fonte em relação à qual todos os deslocamentos são medidos. O deslocamento dela mesma é 0. O padrão é a primeira fonte." },
workflowId: { type: 'string', description: "Um workflow sob o qual esta execução vai ser listada." },
}}
/>

```ts
const { jobId } = await client.edit.audioSync({
sources: [
{ id: "mic", url: micUrl },
{ id: "camA", url: camAUrl },
],
reference: "mic",
})
```

O `output_data.json` do job concluído é um `AudioSyncResult`:

```ts
{
version: number
reference: string // the source every offset is measured against
offsets: Array<{
sourceId: string
offsetMs: number              // referenceMs = sourceMs + offsetMs
confidence: number            // 0 to 1; below 0.5 a note asks you to check by ear
driftMsPerHour: number | null // measured, never corrected; null when the overlap was too short
}>
notes: string[] // low confidence, drift above 33 ms over the overlap, no shared sound
}
```

Com a gravação principal como `reference`, cada `offsetMs` é exatamente o `offsetMs` dessa fonte na sua EDL. Uma requisição malformada é recusada com um `NodaroError` cujo `code` é `validation_error`, antes que qualquer crédito seja reservado. Isso inclui menos de 2 ou mais de 6 fontes, um id repetido ou um `reference` que não seja um dos ids.

### edit.editPlan(input)
Planeja uma edição a partir de uma transcrição com tempos, como faz o nó [Plano de edição](https://nodaro.ai/docs/nodes/video/edit-plan). Escreve um de três tipos de plano: um corte mais enxuto da gravação inteira, um conjunto de clipes curtos ou capítulos.

```ts
editPlan(input: EditPlanInput): Promise<{ jobId: string }>
```

<TypeTable
type={{
mode: { type: '"tighten" | "clips" | "chapters"', required: true, description: "tighten remove pausas e muletas de linguagem, clips corta clipes curtos, chapters divide a gravação em capítulos." },
planTier: { type: '"economy" | "standard" | "premium"', required: true, description: "O nível do modelo. Define a qualidade e o preço." },
transcript: { type: 'Transcript', required: true, description: "A transcrição com tempos: o output_data.json de um job de transcrição." },
sources: { type: 'EditPlanSource[]', required: true, description: "De 1 a 6 fontes. Cada uma é { id, url, kind, role?, speakers?, offsetMs? }, em que kind é video ou audio." },
silence: { type: 'SilenceRanges', description: "O output_data.json de um job de silenceDetect. Passe o objeto inteiro: um objeto sem ranges é ignorado." },
instructions: { type: 'string', description: "Instruções de edição em texto livre." },
styleGuide: { type: 'string', description: "Um guia de estilo a seguir." },
count: { type: 'number', description: "Modo clips: quantos clipes cortar." },
targetDurationSec: { type: 'number', description: "Modo clips: a duração-alvo de cada clipe." },
targetAspect: { type: '"16:9" | "9:16" | "1:1" | "4:5"', description: "O formato do clipe." },
platform: { type: 'string', description: "A plataforma a que os clipes se destinam." },
workflowId: { type: 'string', description: "Um workflow sob o qual esta execução vai ser listada." },
}}
/>

```ts

const { jobId } = await client.edit.editPlan({
mode: "clips",
planTier: "standard",
transcript,
sources: [{ id: "ep", url: masterUrl, kind: "video", role: "master-audio" }],
silence,
count: 5,
targetAspect: "9:16",
})
const { data } = await client.jobs.getStatus(jobId) // poll until completed
const clips = unwrapEditPlanOutput(data.output_data) // one EDL per clip
```

Leia a saída do job concluído com **`unwrapEditPlanOutput()`**. Essa função retorna um `Edl` no modo `tighten`, um array de `Edl` no modo `clips` e um `ChapterSet` no modo `chapters`.

Em uma instalação self-hosted, este método precisa de uma [conexão com o Nodaro Cloud](https://nodaro.ai/docs/self-hosting/cloud-connect). Sem ela, falha com `503 nodaro_connection_required`.

### edit.applyEdl(input)
Renderiza uma lista de decisões de edição em um vídeo ou um arquivo de áudio, como faz o nó [Aplicar EDL](https://nodaro.ai/docs/nodes/video/apply-edl).

```ts
applyEdl(input: ApplyEdlInput): Promise<{ jobId: string }>
```

<TypeTable
type={{
edl: { type: 'Edl', required: true, description: "A lista de decisões de edição a renderizar. A mídia vem da url de cada uma das fontes dela." },
sources: { type: 'string[]', description: "URLs de mídia que substituem as fontes da EDL, na mesma ordem." },
transcript: { type: 'Transcript', description: "Uma transcrição a mover para a linha do tempo editada. O resultado é retornado na saída json do job." },
output: { type: '"video" | "audio"', default: '"video"', description: "O que renderizar." },
quality: { type: '"proxy" | "final"', default: '"final"', description: "proxy renderiza uma prévia rápida. final renderiza com qualidade total." },
crossfadeMs: { type: 'number', default: '0', description: "Um crossfade em cada corte sem transição própria, em milissegundos. 0 significa cortes secos." },
workflowId: { type: 'string', description: "Um workflow sob o qual esta execução vai ser listada." },
}}
/>

```ts
const { jobId } = await client.edit.applyEdl({ edl, output: "video", quality: "proxy", crossfadeMs: 80 })
```

A EDL é verificada antes que qualquer crédito seja reservado. Uma fonte desconhecida, um segmento sem imagem em uma edição de vídeo ou mais de **180 minutos de saída** em uma única renderização falham com um `NodaroError` cujo `code` é `invalid_edl`. A duração é medida depois dos crossfades. O `message` indica o problema, por exemplo, que a edição renderiza 200 minutos e precisa ser dividida em partes de no máximo 180 minutos.

### edit.remapTranscript(edl, transcript)
Move uma transcrição para a linha do tempo de uma edição, **localmente, sem requisição**. Descarta as palavras dentro dos trechos cortados, recorta as palavras que atravessam um corte e desloca todos os tempos para a saída editada. `applyEdl()` faz o mesmo no servidor quando você passa um `transcript` para ele.

```ts
remapTranscript(edl: Edl, transcript: Transcript): Transcript
```

<TypeTable
type={{
edl: { type: 'Edl', required: true, description: "A edição." },
transcript: { type: 'Transcript', required: true, description: "A transcrição da gravação original." },
}}
/>

```ts
const editedTranscript = client.edit.remapTranscript(edl, transcript)
// caption the edited video without another transcription
```

Use-o para legendar uma edição sem renderizar e sem uma segunda transcrição. Ele também é a opção mais rápida para uma transcrição grande quando você só precisa dos novos tempos.

## Frequently asked questions

### Como cortar as pausas de um podcast com o SDK do Nodaro?

Transcreva a gravação, execute client.edit.silenceDetect e depois client.edit.editPlan no modo tighten, com a transcrição e os trechos de silêncio. Renderize o plano retornado com client.edit.applyEdl.

### O que é uma EDL?

Uma lista de decisões de edição (edit decision list): as fontes de uma edição e os segmentos a manter, em ordem, com as transições. client.edit.editPlan escreve uma EDL, e client.edit.applyEdl a renderiza em um vídeo ou um arquivo de áudio.

### Qual é a duração máxima de uma renderização de EDL?

No máximo 180 minutos de saída por renderização. Uma edição mais longa é recusada com invalid_edl antes que qualquer crédito seja reservado. Divida-a em partes.

### Quanto custa client.edit.audioSync?

11 créditos por fonte depois da primeira: 11 créditos para 2 gravações, 33 para 4 e 55 para 6.
