# Variações e lote

Para fazer versões comparáveis de uma edição aprovada (outra abertura, outra primeira frase, outra ordem da
demonstração, outro enquadramento, outra chamada) e para liberar muitas versões de uma vez, com rastreio do que
mudou em cada uma.

## A base

1. Ache a versão aprovada e as fontes dela: o projeto, o corte, os arquivos de `assets/`, o render aprovado.
2. Mais de uma candidata e nenhuma claramente aprovada: resolva com a pessoa antes de fazer qualquer cópia.
3. Só chame de aprovada o que a pessoa aprovou, ou o que passou por um critério que ela já autorizou. Sua
   conferência não é aprovação dela.
4. A base nunca é sobrescrita. Cada versão é uma pasta e um arquivo novos.

## Uma hipótese por versão

- Cada versão muda **uma** coisa, e você escreve por que essa mudança pode mudar o entendimento ou a resposta de
  quem assiste. Hipótese não é previsão de desempenho.
- Se, para ficar coerente, a versão precisa mudar várias coisas, ela ganha o rótulo **combinação** (ou **direção
  nova**), e o resultado dela, quando existir, não pode ser creditado a uma mudança só.
- Não cruze todas as opções. Priorize diferenças que se percebem e dá para comparar. Trocar um detalhe de animação
  gera arquivo, não gera hipótese.
- **Mudou só o que está escrito?** Use a gravação da base; ninguém precisa regravar.
- **Mudou o que a pessoa diz?** Então tem que existir o som daquela frase: uma tomada nova que ela gravou, ou um
  áudio que ela liberou com essas palavras exatas. Legenda nova por cima da fala velha não resolve, e a transcrição
  da frase antiga não pode continuar valendo para a nova. É o caso do gancho que só existe no roteiro: enquanto a
  tomada não chega, ele fica na planilha como `falta gravar` e não vira vídeo.
- Ajustes de corte, duração, legenda e sincronia que a fonte nova obriga são dependências técnicas, anotadas na
  planilha. Não contam como segunda hipótese.
- Tudo que é igual entre as versões vem da base: o mesmo `projeto.json` copiado, as mesmas peças, os mesmos sons.
  O que é da oferta (preço, condição, chamada) fica anotado à parte do que é visual.
- Duração nova atualiza os tempos: o cartão final nunca entra antes da última frase.

## Resultado de campanha

- Número de campanha só entra se vier de relatório de verdade (a pessoa mandou, ou você leu com permissão),
  anotando de quando a quando e em que condições.
- Se público, verba ou entrega foram diferentes entre as versões, a comparação não prova causa.
- Sem medição, escreva "não medido" e não eleja vencedora. Entre versões, compare só o que dá para ver e entender.

## Como montar

1. Copie a pasta do projeto da base para uma pasta por versão, com o código no nome (`sala` para
   `sala_V02_gancho-pergunta`).
2. Mude só a variável. Fala nova passa pelo mesmo caminho do corte da skill; o corpo aprovado fica igual.
3. Nome de arquivo que diz o que mudou: `[PRODUTO]_V02_gancho-pergunta.mp4`. Nunca "versao-certa", "final2" ou
   "ultimo".
4. Preencha a planilha de versões (`fichas/planilha-de-versoes.csv`) e o registro de materiais
   (`fichas/registro-de-materiais.json`).

## Primeiro a mais trabalhosa

Antes das outras, faça uma versão só: a que tem a mudança mais trabalhosa. Compare com a base, no mesmo trecho e
nas mesmas condições:

- só mudou o que estava planejado e o que dependia disso;
- a parte comum a todas as versões e as emendas dela continuam iguais: confira o meio e o fim, não só o começo (o
  `comparar_versoes.py` de `references/revisao.md` compara dois renders fora de um trecho);
- oferta, valores e condições batem com a fonte usada;
- o arquivo exportado dela foi conferido por conta própria (formato, duração, som, legenda). A base ter passado
  não prova nada sobre a versão.

Se a pessoa pediu para escolher ou aprovar antes, mostre e espere. Se não pediu, siga para as outras.

## Planilha de versões

Uma linha por versão, nas colunas de `fichas/planilha-de-versoes.csv`. A ficha já traz três exemplos: a base, uma
versão que troca só o texto escrito da abertura e uma que troca só a frase falada (em `falta gravar` até existir a
tomada).

A coluna `situacao` diz em que pé está a versão. Na ordem em que ela anda:

| situação | quer dizer |
|---|---|
| `falta gravar` | depende de uma fala que ainda não foi gravada; nada se monta |
| `pode montar` | todo o material existe, só falta fazer |
| `exportada` | o arquivo saiu do render e ninguém olhou ainda |
| `conferida` | você passou a conferência da amostra no arquivo exportado |
| `fechada` | a pessoa deu o ok, ou a versão passou num critério que ela liberou |

Terminar o render ou a sua conferência não fecha versão nenhuma. Sem a origem do ok escrita em
`origem_da_aprovacao` (a pessoa, em tal mensagem; ou o critério que ela liberou), ela para em `conferida`.

## Registro de materiais

`fichas/registro-de-materiais.json`, junto da planilha:

- o projeto e a base preservada;
- cada fonte com código, caminho relativo, tipo (vídeo, áudio, imagem, fonte tipográfica), papel e origem;
  material de terceiros com a licença ou autorização conhecida, sem presumir;
- o uso no tempo: o intervalo usado da origem e a posição na versão, em segundos (ou quadros com o fps), e
  qualquer mudança de velocidade;
- cada versão com a hipótese, as mudanças, as fontes e onde o arquivo foi salvo;
- a situação de cada versão, com a origem da aprovação;
- sha256 quando há risco de o arquivo ser trocado mantendo o nome (`shasum -a 256 arquivo`).

Nada de senha, token, caminho pessoal absoluto ou dado de conta.

## Lote de ganchos

O caso mais comum: a pessoa grava o corpo uma vez e vários ganchos, para testar começos.

1. Um anúncio completo aprovado é o padrão (o corpo).
2. Cada gancho vira um vídeo: o gancho novo mais o mesmo corpo, com a mesma edição.
3. Só muda a abertura: a fala do gancho e o texto na tela que escreve essa fala. Se o texto na tela disser outra
   coisa além da fala, a versão é combinação.
4. Nome pronto para subir: `[PRODUTO]_G01_[resumo do gancho].mp4`.
5. Planilha própria do lote: `fichas/matriz-de-ganchos.csv`.
6. O G01 é a versão mais trabalhosa: faça, confira e, se a pessoa pediu para ver, espere.

**Quem vence quando o índice, o roteiro e a fala discordam.** A fala gravada. O nome do arquivo, o
`INDICE.txt` ou o roteiro dizem "no editor" e a gravação diz "num editor": o `quote`, a legenda e o texto
na tela do gancho escrevem o que foi dito. Na dúvida entre o Whisper e o ouvido, ouça o trecho e anote em
`palavras-duvidosas.md`; na matriz, `gancho_falado` é a transcrição conferida, não o índice.

### Como montar: um corte e um projeto por versão

Nunca um corte só com os ganchos e o corpo repetidos um atrás do outro: o `verify_cut` não acusa a repetição e
a trilha corre por cima de todas as versões.

1. **Corte de cada versão.** Uma pasta `edit` por versão, ao lado da base (`<VIDEOS>/edit_G01`,
   `<VIDEOS>/edit_G02`...), com a pasta `transcripts/` da base copiada (a transcrição das fontes é a mesma).
   O `edl_manual.json` de cada uma é o trecho (ou trechos) do gancho seguido dos trechos do corpo **copiados
   sem mudar nada** do `edl_manual.json` da base (mesma fonte, mesmo `start`, mesmo `end`, mesmo `beat` e
   `quote`). Corte pela onda, render e `conferir_corte.py` em cada pasta, como em `references/corte.md`.
2. **Confira que o corpo ficou igual**, com a fala transcrita e a duração. Grave este script ao lado dos
   projetos e rode com a base e cada versão:

   ```python
   # comparar_corpo.py  uso: python3 comparar_corpo.py '<EDIT da base>' '<EDIT da versão>'
   # O corpo é o fim comum dos dois cortes: os trechos do edl.json iguais (fonte, começo e fim), do último para trás.
   import difflib, json, re, sys, unicodedata
   from pathlib import Path

   def norm(t):
       t = unicodedata.normalize("NFKD", t.lower())
       return re.findall(r"[a-z0-9]+", "".join(c for c in t if not unicodedata.combining(c)))

   def ler(edit):
       edit = Path(edit)
       edl = json.loads((edit / "edl.json").read_text(encoding="utf-8"))["ranges"]
       tl = json.loads((edit / "cut_timeline.json").read_text(encoding="utf-8"))["segments"]
       words = json.loads((edit / "transcripts" / "cut.json").read_text(encoding="utf-8"))["words"]
       return [(r["source"], round(r["start"], 3), round(r["end"], 3)) for r in edl], tl, words

   (ea, ta, wa), (eb, tb, wb) = ler(sys.argv[1]), ler(sys.argv[2])
   n = 0
   while n < min(len(ea), len(eb)) and ea[-1 - n] == eb[-1 - n]:
       n += 1
   if n == 0:
       sys.exit("o corpo não é o mesmo corte: o último trecho do edl.json é diferente nas duas pastas")
   def corpo(tl, words):
       seg = tl[len(tl) - n]
       t0 = seg.get("audio_out_start", seg["out_start"]) - 0.02
       return [w for w in words if w["start"] >= t0], tl[-1]["out_end"] - seg["out_start"], seg["out_start"]
   (ca, da, ia), (cb, db, ib) = corpo(ta, wa), corpo(tb, wb)
   print(f"corpo: {n} trechos iguais; começa em {ia:.2f} s na base e {ib:.2f} s na versão; "
         f"dura {da:.2f} s e {db:.2f} s")
   if abs(da - db) > 0.04:
       print("  DIFERENTE: a duração do corpo mudou")
   a, b = [x for w in ca for x in norm(w["text"])], [x for w in cb for x in norm(w["text"])]
   dif = [op for op in difflib.SequenceMatcher(a=a, b=b, autojunk=False).get_opcodes() if op[0] != "equal"]
   for op, i1, i2, j1, j2 in dif:
       print(f"  fala diferente: base \"{' '.join(a[i1:i2])}\" / versão \"{' '.join(b[j1:j2])}\"")
   print("fala do corpo igual" if not dif else "ouça esses pontos: pode ser só a transcrição, ou o corpo mudou")
   ```

   Tem que sair `fala do corpo igual` e a mesma duração. Diferença na primeira palavra do corpo quase sempre é
   o J-cut da emenda com o gancho novo (uns quadros) ou a transcrição: ouça a emenda antes de mexer. Anote o
   resultado na coluna `corpo_conferido` da matriz.
3. **Projeto de cada versão.** Copie o projeto da base para `<PROJ>_G01` e troque `"edit"` no `projeto.json`
   para a pasta da versão. Peças presas a uma palavra (`"em": "palavra"`) acompanham sozinhas; tempos escritos em
   segundos no corpo mudam junto com a diferença de duração do gancho (o começo do corpo é o que o script
   mostra): some a diferença, ou prenda a peça a uma palavra. Só as peças do gancho mudam.
4. **Trilha que recomeça em cada versão.** Mixe cada versão no próprio arquivo exportado, com o
   `mixar_trilha.py` rodado uma vez por versão, com as mesmas opções (o mesmo `--entrada-em` contado do começo
   do vídeo, ou do começo do corpo, conforme o combinado). Cada versão começa a música do início. Se a pessoa
   quer um arquivo só para assistir o lote, junte as versões **depois** de mixadas (nunca mixe a trilha num
   arquivo com as versões emendadas: ela correria contínua por todas).
5. **Conferência de cada arquivo:** o `checar_lote.py` abaixo e, se tem tela gravada ou borrão, o
   `conferir_dados.py` em cada um (`references/revisao.md`).

**Pedido pronto**
```
Usa o anúncio aprovado da pasta [PASTA DO ANÚNCIO] como corpo.
Os ganchos estão na pasta [PASTA DOS GANCHOS], um por arquivo.
Monta um vídeo pra cada gancho, com a mesma edição do corpo. Só o gancho muda.
Me mostra primeiro o G01 pronto antes de fazer os outros.
```

## Liberar muitas versões de uma vez

1. Monte a relação (`lote.md` ou uma planilha): código, arquivo para assistir, o que difere da base, duração,
   proporção e situação. No lote, cada item está em `falta ver`, `volta para ajuste`, `ok pelo critério` (passou
   nos critérios que ela liberou) ou `ok da pessoa`.
2. Anote as autorizações que existem. Silêncio não é aprovação, e a sua conferência nunca vira `ok da pessoa`.
3. **A autorização cobre o lote e os critérios:** confira item a item e exporte os que passam, sem pedir
   aprovação um por um.
4. **A pessoa pediu para revisar, ou há uma decisão que muda o vídeo:** entregue prévias que representem o lote,
   junte as dúvidas numa mensagem só e não exporte os finais desses itens.
5. Confira cada arquivo exportado (grave no projeto e rode `python3 checar_lote.py 1080 1920 saida/*.mp4`):

   ```python
   import json, subprocess, sys
   W, H, arquivos = int(sys.argv[1]), int(sys.argv[2]), sys.argv[3:]
   for arq in arquivos:
       try:
           info = json.loads(subprocess.run(["ffprobe", "-v", "error", "-show_streams", "-show_format", "-of", "json", arq],
                                            capture_output=True, text=True, check=True).stdout)
       except (subprocess.CalledProcessError, json.JSONDecodeError):
           print(f"{arq}: FALHOU (não abre)")
           continue
       v = [s for s in info["streams"] if s["codec_type"] == "video"]
       a = [s for s in info["streams"] if s["codec_type"] == "audio"]
       dur = float(info["format"].get("duration", 0))
       erro = subprocess.run(["ffmpeg", "-v", "error", "-i", arq, "-f", "null", "-"], capture_output=True, text=True).stderr.strip()
       problemas = []
       if not v or (v[0]["width"], v[0]["height"]) != (W, H):
           problemas.append(f"quadro {v[0]['width']}x{v[0]['height']}" if v else "sem vídeo")
       if not a:
           problemas.append("sem som")
       if erro:
           problemas.append("não toca até o fim: " + erro.splitlines()[0][:80])
       print(f"{arq}: {dur:.2f} s · {'ok' if not problemas else 'FALHOU: ' + '; '.join(problemas)}")
   ```

   O script diz se abre, se toca até o fim, o tamanho do quadro, se tem som e a duração. Compare a duração com a
   da planilha. A legenda queimada ele não enxerga: olhe um quadro com fala de cada item (ou rode o
   `helpers/texto_na_tela.py` nele).
6. Entregue os arquivos na pasta combinada, cada um com o seu nome, e a relação com a checagem de cada item: o que
   passou, o que falhou e o que não deu para checar.

Não aumente a quantidade combinada. Não publique, não mande para terceiros, não compre mídia, fonte ou serviço, não
inicie teste de anúncio.
