Trabalho da branch feat/revisao-enfases: pipeline de edição por voz ganha alinhamento forçado (whisperx), roteirização por LLM local (Ollama), e a etapa 5 (revisão de frases) passa a refletir de verdade o que é aplicado. - generate_subtitles_by_emphasis: legenda comum cobre o clipe inteiro, legenda dinâmica só nas frases de ênfase, e a comum é desativada (enabled="0") onde a dinâmica cobre, em vez de nunca ser gerada ali. - validate_subtitle_layout ignora títulos com enabled="0" — corrige falso positivo de colisão contra o que está desativado no lugar dele. - Corrige zoom/marcador sendo descartado quando a borda encosta exatamente no início de um corte. - Etapa 5 do Assistente: recarrega quando as decisões da IA mudam (com fresh=true, ignorando a revisão salva antiga) — resolve a dessincronia entre "ativa" na tela e o que já foi cortado no FCPXML. - Etapa "Processar" reaplica as decisões da revisão (_phrase_actions.json) antes da cadeia de remoção de silêncio/legendas — antes, desativar uma frase na etapa 5 não tinha efeito nenhum no vídeo final. - Etapa "Concluído" fundida em "Processar" — abrir no Final Cut/Finder aparece assim que termina, sem slide extra. - Palavra clicável na etapa 5 agora funciona como toggle (clique de novo desfaz) e mostra a própria ênfase (sublinhado colorido + peso da fonte). - fcpxml/forced_align.py, fcpxml/llm_local.py, ai_edit.py: alinhamento fonético via whisperx e roteirização local via Ollama/Gemma. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
77 lines
3.6 KiB
Markdown
77 lines
3.6 KiB
Markdown
# 01 — Leitura do JSON
|
||
|
||
> **Escopo:** Como ler o voice_timeline em camadas, sem recalcular o que já foi medido.
|
||
> **Quando:** Fase 1 — ver a ordem de trabalho em `../SKILL.md`.
|
||
|
||
O arquivo `<mídia>_voice_timeline.json` é a entrada de todo o trabalho.
|
||
Leia em camadas, de cima para baixo, e só desça quando precisar.
|
||
|
||
## Camadas
|
||
|
||
| Camada | O que traz | Para quê |
|
||
|---|---|---|
|
||
| `layers` | o que de fato rodou na análise | **leia primeiro** — ver `07-analise-incompleta.md` |
|
||
| `summary` | forma da peça, `peak_moments`, contagens | visão geral em poucos números |
|
||
| `speakers` | quem fala, % do tempo, frases de exemplo | identificar papéis |
|
||
| `segments` | cada fala com seus agregados | **onde você mais trabalha** |
|
||
| `segments[].words` | detalhe por palavra | achar o instante exato de um destaque |
|
||
| `scales` | o que cada número significa | documentação dentro do próprio arquivo |
|
||
|
||
## Campos que decidem quase tudo
|
||
|
||
**`gap_before`** — silêncio antes da fala, em segundos. É o mapa estrutural
|
||
da gravação: acima de ~3s (`take_boundary: true`) a câmera parou ou a
|
||
tomada recomeçou. Num material real de 3min17s isso identificou 6
|
||
fronteiras, todas exatamente onde a pessoa recomeçava o roteiro.
|
||
|
||
**`take_boundary`** — booleano derivado do `gap_before`. Use para agrupar
|
||
tomadas.
|
||
|
||
**`emphasis`** (0–1) — índice combinado de energia, variação de tom,
|
||
variação de ritmo, pausa anterior e duração. **É relativo ao material
|
||
analisado**, nunca uma medida absoluta. Ver `02-enfase-e-reanalise.md`.
|
||
|
||
**`energy`** (0–1) — intensidade relativa ao trecho mais alto da gravação.
|
||
|
||
**`pitch_delta`** (0–1) — quanto o tom se afasta da média do falante.
|
||
|
||
**`peak_emphasis`** e **`avg_energy`** (por segmento) — permitem julgar uma
|
||
frase inteira sem ler palavra por palavra. É por aqui que você avalia o
|
||
arco narrativo.
|
||
|
||
**`energy_raw`** e **`pitch_hz`** — valores brutos, sem normalização. Não
|
||
use para decidir; existem para permitir a reanálise da Fase 2.
|
||
|
||
## O que NÃO fazer
|
||
|
||
- **Não recalcule** energia, tom ou ênfase. O sistema mede melhor e de
|
||
forma reprodutível.
|
||
- **Não reestime tempos "no olho".** Use os timestamps do JSON.
|
||
- **Não trate `emphasis` como valor absoluto.** Um 0,35 pode ser o pico de
|
||
uma gravação e ruído em outra.
|
||
|
||
## O timestamp por palavra tem um viés conhecido
|
||
|
||
O início de cada palavra vem sistematicamente **adiantado em ~0,3-0,5s** em
|
||
relação ao ataque real da fala — medido em material real com ffmpeg (`astats`),
|
||
consistente em 6 pontos do mesmo vídeo. O fim da palavra não tem esse problema
|
||
(erro de poucos centésimos). Causa: `word_timestamps` do faster-whisper deriva
|
||
por atenção cruzada, sem alinhamento forçado — ver `05_EXPERIENCIAS.md`, entrada
|
||
de 2026-08-19.
|
||
|
||
**Quando o pipeline já corrigiu isso:** se `layers.alignment` for `true`
|
||
(transcript gerado com alinhamento forçado fonético via whisperx, implementado
|
||
depois desse aviso), o viés foi removido na origem — **não aplique o offset
|
||
manual** abaixo. O aviso vale só para transcripts antigos sem `layers.alignment`.
|
||
|
||
Isso não é "reestimar no olho" — é um bug de medição na fonte, não um
|
||
julgamento seu. Na prática (somente sem `layers.alignment`):
|
||
|
||
- Ao posicionar um `zoom` cujo `start` precisa cair exatamente na palavra
|
||
(não uma frase inteira), some **+0,3 a +0,4s** ao timestamp do JSON antes
|
||
de decidir, ou confira com `ffmpeg -af astats` se a precisão importar
|
||
para o frame.
|
||
- **Não aplique essa correção a `gap_before` para decidir corte** — a régua
|
||
de silêncio (`06-texto-corte-marcador.md`) já é conservadora o bastante
|
||
para absorver esse erro; corrigir os dois ao mesmo tempo é redundante.
|