Files
gart/code/Engine/docs/09_MANUTENCAO.md
T
João HenriqueandClaude Sonnet 5 7b5aed79ee feat(voz): legenda por ênfase, forced align, IA local e correções de zoom/revisão
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>
2026-08-21 18:26:04 -04:00

158 lines
7.7 KiB
Markdown

# 09 — Manutenção: onde mexer, o que está aberto, o que dói
> **Escopo:** Por onde começar cada tipo de tarefa, o que está aberto e onde dói.
> **Não cobre:** Como as coisas funcionam — este doc roteia para quem explica
Este é o documento de rota. Os outros descrevem o que **é**; este diz o que
**fazer** e por onde começar quando chega uma implementação, uma melhoria ou
uma correção.
Última varredura: 2026-08-19 · 1.466 testes · lint zerado
---
## 1. Chegou uma tarefa — por onde começo?
| A tarefa é… | Comece em | Não esqueça |
|-------------|-----------|-------------|
| Regra nova de edição (corte, zoom, legenda) | `fcpxml/<módulo>` + teste | Expor na tool **e** na ponte, senão só metade dos usuários alcança |
| Corrigir XML que o FCP recusa | `fcpxml/writer/` + `dtd.py` | Validar contra o DTD real, não só o teste |
| Mudança visível na interface | `MacApp/Sources/` | **Abrir a tela** — compilar não prova nada (§4) |
| Comando novo para o app | `admin/api/<assunto>.py` | Registrar na tabela de `models_api.py` |
| Ferramenta MCP nova | `server_tools/<categoria>.py` | Schema `Tool(...)` + `TOOL_HANDLERS` |
| Ajuste de análise de voz | `fcpxml/voice_*`, `emphasis.py` | Regerar os `_voice_timeline.json` de teste |
| "Está lento" / "está errado" e não sei onde | §5 (mapa de sintomas) | — |
**A pergunta que resolve 90% das dúvidas de lugar:** essa lógica precisa saber
o que é uma tool MCP ou uma tela? Se não precisa — e quase nunca precisa — ela
vai para `fcpxml/`.
---
## 2. O que está aberto agora
Ordenado por quanto atrapalha, não por esforço.
### 2.1 A etapa 6 ignora a revisão de ênfases
O usuário lapida as frases na etapa 5, o `_phrase_review.json` é gravado — e a
etapa 6 ainda processa como antes. Falta ligar: **zoom e legenda dinâmica só
nas frases de ênfase, legenda comum no resto**. É a continuação natural do
trabalho da etapa 5 e o item mais valioso da lista.
→ `MacApp/Sources/WizardView.swift` (`finalizeProcessing`), `admin/api/subtitles.py`,
`fcpxml/phrase_review.py` (`emphasis_spans` já é produzido e ninguém consome).
### 2.2 Offset de ~400 ms no timing por palavra
O faster-whisper sem alinhamento forçado erra o início de cada palavra em
~0,4 s. Isso desloca zoom, corte e `gap_before` de uma vez. Há paliativo
aplicado por projeto; a correção estrutural é ligar o **WhisperX** (ou
alinhamento equivalente) em `transcribe.py`, o que levaria o erro para ~30 ms.
Custo real: regerar todos os `_transcript.json` e `_voice_timeline.json`
existentes. → `05_EXPERIENCIAS.md` #14, estado `parcialmente resolvido`.
### 2.3 `MacApp/` não tem teste automatizado
5.500 linhas de Swift sem uma asserção. A rede hoje é o harness manual (§4) e
o olho do usuário. Não é para sair criando suíte de UI — mas lógica pura que
foi parar na camada de tela (cálculo de trim, mapeamento de tempo) deveria
descer para o Python, onde já existe rede.
### 2.4 `admin/` fica fora do lint
`run_after_fix.sh` roda o ruff de dentro de `code/`, então `admin/` — 1.751
linhas de código que o app depende para funcionar — nunca é verificado.
Incluir mexe no gate, então é decisão consciente, não esquecimento.
### 2.5 Confirmações visuais pendentes no FCP
Várias entradas do `05_EXPERIENCIAS.md` estão marcadas como resolvidas *no XML*
— testes verdes, DTD válido — mas **pendentes de importação real no Final Cut**.
XML válido não é o mesmo que XML que renderiza como o esperado. Ao mexer em
legenda, zoom ou keyframe, a confirmação final é abrir no FCP.
### 2.6 Submódulo `WHISPERX` com conteúdo modificado e não commitado
Está fora dos commits de propósito, porque ninguém verificou o que mudou lá
dentro. Precisa ser olhado e resolvido — ou commitado, ou revertido.
---
## 3. Onde o código ainda é grande (e onde isso não é problema)
Quatro arquivos foram divididos (`writer.py`, `models.py`, `models_api.py`,
`_shared.py`): 6.685 linhas concentradas viraram 43 módulos.
O que sobrou grande, e o diagnóstico honesto de cada um:
| Arquivo | Linhas | Vale dividir? |
|---------|-------:|---------------|
| `fcpxml/text_layout.py` | 901 | **Não.** É diagramação — um assunto coeso. |
| `fcpxml/rough_cut.py` | 798 | **Não.** É geração de timeline, um assunto. |
| `fcpxml/model_manager.py` | 748 | Talvez: mistura catálogo, download e config. |
| `server_tools/voice.py` | 754 | Talvez, se crescer mais. |
| `MacApp/TranscriptionView.swift` | 843 | Sim, quando for mexer nela. |
| `MacApp/WizardView.swift` | 808 | Sim: sete etapas num `switch` só. |
**Critério, não número:** divida quando o arquivo tiver **assuntos** que não se
falam. Um arquivo grande de um assunto só é mais fácil de ler que seis arquivos
pequenos que você precisa abrir juntos. Código picado sem motivo atrapalha tanto
quanto arquivo gigante.
---
## 4. Checklist antes de dar algo por pronto
```bash
cd code && ./Engine/run_after_fix.sh # lint zerado + 1.466 testes
admin/run_app.command # se mexeu no app (padrão de revisão)
```
E, além do script:
- [ ] **Mexeu na interface? Abriu a tela?** Compilar não prova que roda —
`VideoPlayer` compilava e abortava (`05_EXPERIENCIAS.md` #22).
- [ ] **Mexeu em XML? Importou no FCP?** DTD válido ≠ renderiza certo.
- [ ] **Dividiu ou moveu módulo?** Procure `patch('<módulo>.` e imports
relativos dentro de funções — é o que quebra em silêncio (#23).
- [ ] **Criou teste fora de `code/tests/`?** Confirme que a contagem total
subiu. Teste fora de `testpaths` não roda e dá falsa sensação de rede (#24).
- [ ] **Problema estrutural ou erro recorrente?** Registre em
`05_EXPERIENCIAS.md` com o índice atualizado.
- [ ] **Documentação divergiu?** Corrija no mesmo commit. Doc velha engana mais
que doc ausente.
---
## 5. Mapa de sintomas → onde olhar
| Sintoma | Suspeite de | Arquivo |
|---------|-------------|---------|
| FCP recusa o arquivo ao importar | `id` inválido, ordem de filhos, timebase | `writer/validation.py`, `dtd.py` |
| Título importa mas não aparece | Template/uid Motion que não resolve | `writer/titles.py` |
| Corte no lugar errado | Tempo pós-corte usado como se fosse original | `voice_actions.py` (`shift_after_cuts`) |
| Zoom no lugar errado | Idem, ou offset de timing do Whisper | §2.2 |
| Legenda sobrepondo | Layout ou conteúdo antigo no arquivo | `collision.py`, `text_layout.py` |
| "Ênfase" apontando para palavra à toa | Falta renormalizar após o corte | `refine_voice_timeline` |
| App diz que falta librosa/pyannote | `uv run` com cwd errado | `PythonBridge.swift` (§3 do doc 08) |
| App crasha com `ModuleNotFoundError: server_tools` | `sys.path` de `admin/api/` mal calculado | `05_EXPERIENCIAS.md` #25 |
| Tela do app fecha o programa | Componente de framework que só falha em runtime | `05_EXPERIENCIAS.md` #22 |
| Comando existe no MCP mas não no app | Falta expor na ponte | `admin/api/`, #20 |
---
## 6. Convenções que não são negociáveis
Estão em `01_ARCHITECTURE.md` §2 e valem repetir as três que mais custaram:
1. **Tempo é fração racional.** Float para tempo produz drift que só aparece
depois de dez operações encadeadas.
2. **Ação de voz é sempre em tempo da mídia original.** Nunca pós-corte.
3. **Original nunca é sobrescrito.** Toda saída ganha sufixo.
---
## Documentos relacionados
- [01 Arquitetura](01_ARCHITECTURE.md) — camadas e onde cada coisa mora
- [02 Módulos](02_MODULES.md) — mapa do engine, módulo a módulo
- [03 Server/Tools](03_SERVER_TOOLS.md) — as 77 ferramentas MCP
- [04 Testes & Workflow](04_TESTS_AND_WORKFLOW.md)
- [05 Experiências](05_EXPERIENCIAS.md) — o que já quebrou e por quê
- [06 Boas Práticas](06_BOAS_PRATICAS.md)
- [08 App macOS](08_APP_MACOS.md) — o app e o Assistente