refactor: writer.py vira pacote, um módulo por assunto

O writer tinha 4.199 linhas, das quais 3.300 numa única classe com dezoito
assuntos dentro. Achar o trecho de zoom exigia rolar por marcadores,
velocidade e legendas.

Agora é o pacote fcpxml/writer/, com um arquivo por assunto e o
FCPXMLModifier montado por composição de mixins. Mixins, e não objetos
separados, porque todas essas operações mexem no mesmo documento e nos
mesmos índices — separá-las em objetos independentes transformaria toda
chamada interna em travessia de fronteira sem nada em troca. A divisão que
importa aqui é de leitura, não de estado.

Nenhuma mudança de comportamento e nenhuma alteração nos ~50 pontos que
importam do writer: o __init__ re-exporta tudo, inclusive os nomes com
underscore que a suíte já usava.

    core      723   carga, índices, navegação na spine, save
    titles    600   títulos e legendas dinâmicas
    cut       333   dividir, cortar faixas, apagar
    speed     297   velocidade e zoom
    (+ 20 módulos menores)

Único ajuste de chamada: quatro testes faziam patch em
fcpxml.writer.subprocess, que agora mora em writer.document (ver
Engine/docs/05_EXPERIENCIAS.md #23).

Lint zerado, 1441 testes passando.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
João Henrique
2026-08-19 21:38:49 -04:00
co-authored by Claude Opus 5
parent 1bebee4359
commit 4f5cf94443
28 changed files with 4719 additions and 4203 deletions
+28
View File
@@ -1228,6 +1228,33 @@ o outro; percentil entrega um punhado útil nos dois casos.
---
## 23 — 2026-08-19 — Dividir um módulo em pacote quebra quem faz `patch` nele
- **Sintoma:** ao transformar `fcpxml/writer.py` (4.199 linhas) no pacote
`fcpxml/writer/`, quatro testes passaram a falhar com
`AttributeError: module 'fcpxml.writer' has no attribute 'subprocess'` —
embora nenhuma linha de lógica tivesse mudado.
- **Causa raiz:** os testes usavam `@patch('fcpxml.writer.subprocess.run')`.
Isso não depende da API pública, e sim de *onde o import mora*: com o
módulo dividido, `subprocess` passou a ser importado por
`fcpxml/writer/document.py`, então o alvo do patch deixou de existir.
Re-exportar no `__init__` não resolveria — substituir
`fcpxml.writer.subprocess` não afeta a referência que `document` já tem.
- **Solução adotada:** apontar o patch para o módulo real
(`fcpxml.writer.document.subprocess.run`). Duas armadilhas do tipo foram
evitadas antes: imports relativos precisam de um ponto a mais ao descer um
nível (`from .models` → `from ..models`), inclusive os que ficam *dentro*
de funções, e o `__all__` precisa listar os nomes com underscore que o
resto do projeto já importava, senão a divisão vira quebra de API.
- **Aprendizado:** a suíte protege comportamento, não localização. Antes de
dividir um módulo, procure por `patch('<modulo>.` e por imports relativos
escondidos dentro de funções — são as duas coisas que uma refatoração
puramente mecânica quebra em silêncio, e as únicas que os testes pegam
tarde.
- **Estado:** `resolvido`
---
## Resumo rápido (índice)
| # | Data | Problema | Estado |
@@ -1252,5 +1279,6 @@ o outro; percentil entrega um punhado útil nos dois casos.
| 20 | 2026-08-19 | `apply_voice_actions` ausente da ponte e do encadeamento do app — dava para analisar e legendar, não para cortar | `resolvido` |
| 21 | 2026-08-19 | Teste ainda afirmava o default `zoom scale=1.3` removido do parser (agora vem do `zoom_scale` do usuário) | `resolvido` |
| 22 | 2026-08-19 | `VideoPlayer` (AVKit) aborta em runtime no app compilado por `swiftc` — etapa 5 fechava o app; trocado por `AVPlayerLayer` | `resolvido` |
| 23 | 2026-08-19 | Dividir `writer.py` em pacote quebrou `@patch('fcpxml.writer.subprocess')` — a suíte protege comportamento, não localização | `resolvido` |
> Mantenha o índice acima sempre sincronizado com as entradas mais recentes.