> ## Documentation Index
> Fetch the complete documentation index at: https://zapo.to/llms.txt
> Use this file to discover all available pages before exploring further.

# Mídia

> Envie imagens, vídeo, áudio de voz, documentos e figurinhas — e faça stream ou download de mídias recebidas do WhatsApp com os helpers de mídia do zapo.

A mídia é enviada pelo mesmo método `client.message.send`, usando um objeto de conteúdo de mídia tipado. O builder preenche para você os campos gerenciados pelo protocolo (chaves de criptografia, digests SHA-256, direct path, upload) — você fornece a fonte e, opcionalmente, um `mimetype`.

<Warning>
  Para mídia **utilizável**, instale [`@zapo-js/media-utils`](/pt-br/installation#sending-media) e conecte um processor via opção de client [`media`](#media-processing). A mídia ainda faz upload sem ele, mas sem um processor ela fica **sem thumbnail/preview, dimensões ou waveform** — então pode chegar como anexo simples.
</Warning>

<h2 id="mimetype-resolution">
  Resolução de mimetype
</h2>

`mimetype` é opcional. O builder resolve nesta ordem:

1. O `mimetype` que você passa no objeto de conteúdo vence.
2. Se um `WaMediaProcessor` com `detectMimetype` está configurado, o builder o chama (sniffing de magic bytes). `@zapo-js/media-utils` implementa isso em cima de [`file-type`](https://github.com/sindresorhus/file-type) ^19 — instale `file-type` para habilitar a detecção.
3. Caso contrário, o builder lança erro para mensagens `image`/`video`/`audio`/`document`/`ptv`.

Stickers usam `image/webp` por padrão quando nenhum `mimetype` é definido. Entradas de stream `Readable` sem mimetype são staged para um arquivo temporário antes da detecção rodar.

<h2 id="media-input">
  Entrada de mídia
</h2>

O campo `media` aceita vários tipos de entrada:

```ts theme={null}
type MediaInput = Uint8Array | ArrayBuffer | Readable | string
```

<Tip>
  **Prefira um caminho de arquivo (`string`) ou um stream `Readable` em vez de um `Buffer`/`Uint8Array`.** O `zapo` faz o streaming da mídia pelo pipeline sem bufferizar o arquivo inteiro em memória — passar um caminho ou stream mantém a memória plana independentemente do tamanho do arquivo. Ler um arquivo grande inteiro para um `Buffer` primeiro anula isso e é desencorajado. (`Buffer` também é evitado internamente em favor de `Uint8Array`.)
</Tip>

<h2 id="images">
  Imagens
</h2>

```ts theme={null}
// Preferido — passe um caminho de arquivo; o zapo faz o streaming
await client.message.send(jid, {
  type: 'image',
  media: './photo.jpg',
  mimetype: 'image/jpeg',
  caption: 'A photo'
})

// Ou um stream Readable (ex.: a partir de uma resposta HTTP)
import { createReadStream } from 'node:fs'

await client.message.send(jid, {
  type: 'image',
  media: createReadStream('./photo.jpg'),
  mimetype: 'image/jpeg'
})
```

<h2 id="video">
  Vídeo
</h2>

```ts theme={null}
await client.message.send(jid, {
  type: 'video',
  media: './clip.mp4',
  mimetype: 'video/mp4',
  caption: 'A clip',
  gifPlayback: false
})
```

Para uma mensagem redonda de **push-to-video** (PTV), use `type: 'ptv'` com o mesmo formato.

<h2 id="audio--voice-notes">
  Áudio e notas de voz
</h2>

```ts theme={null}
// Áudio comum
await client.message.send(jid, {
  type: 'audio',
  media: './song.mp3',
  mimetype: 'audio/mpeg'
})

// Nota de voz (push-to-talk)
await client.message.send(jid, {
  type: 'audio',
  media: './voice.ogg',
  mimetype: 'audio/ogg; codecs=opus',
  ptt: true
})
```

<Tip>
  Notas de voz são melhor renderizadas como Opus em um container OGG. Habilite o [processamento de mídia](#media-processing) para gerar waveforms automaticamente e normalizar notas de voz.
</Tip>

<h2 id="documents">
  Documentos
</h2>

```ts theme={null}
await client.message.send(jid, {
  type: 'document',
  media: './report.pdf',
  mimetype: 'application/pdf',
  fileName: 'Q3 Report.pdf',
  caption: 'The quarterly report'
})
```

<h2 id="stickers">
  Figurinhas
</h2>

```ts theme={null}
await client.message.send(jid, {
  type: 'sticker',
  media: await readFile('./sticker.webp'),
  mimetype: 'image/webp'
})
```

Para um **pacote de figurinhas** completo, use `type: 'sticker-pack'` com `stickers`, um `trayIcon` e os metadados do pacote (`stickerPackId`, `name`, `publisher`).

<h2 id="view-once">
  Visualização única
</h2>

Envolva imagem/vídeo/áudio como visualização única com a opção de envio:

```ts theme={null}
await client.message.send(jid, {
  type: 'image',
  media: './secret.jpg',
  mimetype: 'image/jpeg'
}, {
  viewOnce: true
})
```

<h2 id="downloading-incoming-media">
  Baixando mídia recebida
</h2>

O coordinator de mensagens descriptografa e baixa a mídia de um evento recebido. Três variantes estão disponíveis — **prefira as de streaming**:

```ts theme={null}
client.on('message', async (event) => {
  if (!event.message?.imageMessage) return

  // Preferido — faz streaming para um arquivo (memória constante)
  await client.message.downloadToFile(event, './incoming.jpg')

  // Ou consuma o stream Readable você mesmo
  const stream = await client.message.download(event)

  // Evite para mídia grande — bufferiza o arquivo inteiro em memória
  const bytes = await client.message.downloadBytes(event)
})
```

<Tip>
  `download()` / `downloadToFile()` fazem o streaming da mídia e mantêm a memória plana independentemente do tamanho. `downloadBytes()` materializa o arquivo inteiro em memória — recorra a ele apenas para mídias pequenas e limite-o com `maxBytes`.
</Tip>

As três aceitam tanto um `WaIncomingMessageEvent` quanto um `Proto.IMessage` bruto, mais `WaDownloadMediaOptions` opcional (por exemplo `maxBytes` para limitar `downloadBytes`).

<h3 id="without-a-connected-client">
  Sem um client conectado
</h3>

`downloadMediaMessage` é uma função livre que espelha o `client.message.download` mas não precisa de uma sessão pareada. O metadata de mídia encriptada viaja dentro da própria mensagem (já decifrada), então você consegue re-baixar mídia de um evento persistido muito tempo depois do socket original ter ido embora — útil para workers offline, replay de arquivo, ou qualquer coisa que processe mensagens armazenadas sem subir um `WaClient`.

```ts theme={null}
import { downloadMediaMessage } from 'zapo-js'
import { createWriteStream } from 'node:fs'

const stream = await downloadMediaMessage(event)
stream.pipe(createWriteStream('photo.jpg'))
```

Aceita um `WaIncomingMessageEvent` ou um `Proto.IMessage` bruto, retorna um `Readable` cujo dono é você (pipe ou `.destroy()` — um stream não consumido vaza o socket). Verificação MAC + SHA-256 roda conforme os bytes são consumidos, mesma semântica do método do coordinator. Lança quando a mensagem não tem mídia para download.

| Opção                               | Tipo                          | Notas                                                                                                                                                                                                                                                                                         |
| ----------------------------------- | ----------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `transfer`                          | `WaMediaTransferClient`       | Reutilize um transfer client existente — herda os proxy agents, timeouts e toggle de verificação MAC dele, e evita subir um novo HTTP client por chamada num loop. Um stateless é criado quando omitido (bom para chamadas pontuais).                                                         |
| `proxy`                             | `WaProxyTransport`            | Proxy por chamada para a perna de download do CDN, espelhando o `proxy.mediaDownload` do client. O fetch roda em `node:http`/`node:https`, então só a forma `http.Agent` é honrada — um dispatcher undici é ignorado. Tem precedência sobre o agent default de um `transfer` que você passar. |
| `signal` / `timeoutMs` / `maxBytes` | (de `WaDownloadMediaOptions`) | Mesma semântica dos métodos do coordinator.                                                                                                                                                                                                                                                   |

Para acesso de mais baixo nível — quando você quer fazer o fetch ao CDN você mesmo, passar as chaves para outro processo, ou só inspecionar o que é baixável — o `resolveMediaPayload` retorna as keys + hashes sem fazer I/O:

```ts theme={null}
import { resolveMediaPayload } from 'zapo-js'

const payload = resolveMediaPayload(event.message)
// → WaResolvedMediaPayload | null
// { mediaType, directPath, mediaKey, fileSha256?, fileEncSha256?, mimetype?, fileLength? }
```

Retorna `null` quando a mensagem não tem mídia para download, ou quando o proto não carrega `directPath` / `mediaKey`. Desencapsula `ephemeralMessage`, `viewOnceMessage` / `viewOnceMessageV2` e `documentWithCaptionMessage` antes de resolver. Tipos suportados: `image`, `video` (`gif` quando `gifPlayback`), `audio` (`ptt` quando `ptt`), `document`, `sticker`, `ptv`.

<Warning>
  `payload.mediaKey` é a seed AES/MAC do blob encriptado — trate como segredo. Não logue, não inclua em mensagens de erro, e não envie para serviço de terceiros a menos que esse seja o ponto inteiro do seu pipeline.
</Warning>

<h2 id="media-processing">
  Processamento de mídia
</h2>

Para mídia adequada, use um **processador de mídia**. Instale `@zapo-js/media-utils` e passe um através da opção de client `media` — ele sonda e processa a mídia (dimensões, duração, miniaturas, waveforms, normalização de notas de voz) antes do upload. Sem ele, a mídia ainda faz upload mas sem esse processamento:

```ts theme={null}
import { createMediaProcessor } from '@zapo-js/media-utils'

const client = new WaClient({
  store,
  sessionId: 'default',
  media: {
    processor: createMediaProcessor(),
    generateThumbnail: true,
    generateWaveform: true,
    normalizeVoiceNote: true
  }
}, logger)
```

<Note>
  `@zapo-js/media-utils` invoca `ffmpeg`/`ffprobe` e usa `sharp`. Garanta que esses binários estejam disponíveis no seu ambiente.
</Note>
