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

# Biblioteca de mídia

> Onde o arquivo do club mora, quem o usa, e como ele chega ao aluno.

Cada club tem uma biblioteca. O arquivo é uma linha em `media`, e quem o usa —
a aula, a capa do curso, a marca do club — aponta para essa linha. Reuso sai de
graça: a mesma mídia em dois lugares são dois donos apontando para a mesma
linha, não duas cópias.

## Pastas

Árvore de até seis níveis, com `parent_id`. O nome é único dentro da mesma
pasta. Mover uma pasta para dentro dela mesma — ou de uma subpasta dela — é 422
`invalid_folder`: seria um ciclo, e o ramo sumiria da árvore.

Apagar uma pasta **sobe** as pastas de dentro e as mídias um nível. Conteúdo não
some porque alguém organizou a biblioteca.

Uma mídia está em uma pasta. Estar em vários lugares ao mesmo tempo é etiqueta,
e etiqueta ainda não existe.

## Enviar

Vídeo mora na **Bunny Stream**, que transcodifica e entrega HLS. Imagem, áudio e
documento moram no **R2**. Os dois sobem **direto do browser**, e o arquivo
nunca passa pela API.

| o que                      | como                                                                                                                |
| -------------------------- | ------------------------------------------------------------------------------------------------------------------- |
| vídeo                      | `POST /media` devolve `upload` com `method: tus` — envio em pedaços, que retoma de onde parou                       |
| imagem, áudio, documento   | `POST /media` devolve `upload` com `method: put` — um `PUT` assinado no bucket; depois, `POST /media/{id}/complete` |
| YouTube, Vimeo, ou uma URL | `POST /media` com `provider` e `external_id`: nada sobe, e a mídia nasce pronta                                     |

A chave da conta nunca vai para o browser: o que viaja é uma assinatura, que
vale para **um** arquivo e por pouco tempo.

Na conclusão do arquivo, quem confirma é o **bucket** — a API pergunta a ele se
o objeto existe, qual o tamanho e qual o tipo. "Enviado" não é a palavra de quem
enviou: sem essa conferência, um envio interrompido viraria uma aula com
material que não abre.

É ali também que o **limite de tamanho** vale (500 MB por arquivo, por padrão):
a assinatura autoriza o objeto, não o tamanho dele. O que passa do teto é
apagado do bucket e a mídia fica `failed`, com o motivo.

Envio que nunca termina não fica "enviando" para sempre: uma varredura pergunta
ao bucket de tempos em tempos, recupera o que chegou atrasado e dá por perdido o
que não chegou depois de 24 horas.

## Pública ou privada

Toda mídia nasce **privada**. Publicar é escolha explícita, e a diferença é de
**bucket**: objeto privado não tem endereço público, então nenhum erro de código
consegue publicá-lo.

|              | privada                             | pública                      |
| ------------ | ----------------------------------- | ---------------------------- |
| endereço     | assinado, expira em horas           | estável, sem assinatura      |
| cache do CDN | nenhum (a URL muda a cada resposta) | sim                          |
| serve para   | aula, material, tudo que é pago     | capa, marca do club, trailer |

O que o endereço estável resolve: a prévia de link do WhatsApp busca a imagem
dias depois; a capa dentro de um e-mail precisa continuar abrindo; e o CDN só
cacheia o que não muda. Uma URL assinada falha nos três.

**Vídeo também pode ser público** — é o trailer do curso ou a aula de
demonstração, entregue sem token para quem ainda não comprou.

Trocar a visibilidade **move o arquivo entre os buckets**, server-side: os bytes
não passam pela API. Com uma trava: publicar uma mídia que está em uma aula é
recusado com 409 `media_in_use`. Material pago que vira público por um clique
distraído é o erro que não se desfaz — quem baixou, baixou.

## Imagem

Imagem pública é entregue **redimensionada pelo CDN**, em dois tamanhos fixos:
400 px para grade e card, 1600 px para a página. O formato é escolhido pelo
browser (AVIF ou WebP), e imagem menor que o pedido não é ampliada.

Nada é gerado no envio nem guardado: a transformação acontece no primeiro acesso
e fica no cache. Os tamanhos são fixos de propósito — cada largura diferente é
uma entrada nova no cache, e duas variantes bem escolhidas acertam quase sempre.

Imagem **privada** sai no tamanho original: a URL assinada aponta para fora da
zona do CDN, e cada resposta tem uma assinatura diferente, então não haveria
cache a aproveitar.

## Estados

`uploading` → `processing` → `ready`, ou `failed` com o motivo em `error`. O
provedor avisa por webhook quando termina, e o aviso **não é acreditado**: ele
serve como "vá olhar este vídeo", e o que é gravado vem da consulta à API dele.
Se o aviso se perder, uma varredura pergunta de novo — mídia parada não fica
parada para sempre.

## Entrega

O endereço do arquivo é assinado **no momento em que a resposta sai**, com prazo
curto, e depois de o direito e a agenda terem decidido. Ele não é guardado em
coluna nem em cache: link assinado dentro de cache é link que expira guardado.

Para arquivo, toda entrega é assinada por construção: o bucket não é público.
Para vídeo, `signed: false` significa que a zona está sem chave de token e o
arquivo é servido a quem souber a URL — a resposta diz isso em vez de fingir
proteção.

## Tirar da biblioteca

* **Arquivar** tira do seletor e a mídia **continua sendo entregue** onde já
  está em uso. É o que fazer na dúvida.
* **Excluir** apaga também no provedor, e só funciona com **zero usos**: se
  alguma aula, anexo, capa ou marca aponta para ela, a resposta é 409
  `media_in_use` com a lista de onde.

`GET /media/{id}/usage` responde essa pergunta a qualquer momento.


## Related topics

- [Nome e endereço do club](/api-reference/clubs/update.md)
- [Criar club](/api-reference/clubs/create.md)
- [Aceitar convite](/api-reference/clubs/accept-invitation.md)
- [Club da sessão](/api-reference/clubs/get.md)
- [Exclui a mídia](/api-reference/media/delete.md)
