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>
178 lines
8.8 KiB
Markdown
178 lines
8.8 KiB
Markdown
# 01 — Arquitetura do Sistema (G-ART / fcp-mcp-server)
|
|
|
|
> **Escopo:** Como o sistema é dividido em camadas e onde cada responsabilidade mora.
|
|
> **Não cobre:** Detalhe módulo a módulo (→ 02) · ferramentas MCP (→ 03) · app (→ 08)
|
|
|
|
> Referência canônica de como o sistema está dividido. Leia antes de qualquer
|
|
> mudança de código. Se algo aqui divergir do código, **o código está certo e
|
|
> este documento está velho** — corrija-o no mesmo commit.
|
|
|
|
Última varredura: 2026-08-19 · 77 ferramentas MCP · 1.498 testes · versão `0.6.35`
|
|
|
|
---
|
|
|
|
## 1. Visão de cima (camadas)
|
|
|
|
O sistema lê, analisa e reescreve **FCPXML** do Final Cut Pro. Ele opera *fora*
|
|
do FCP: você exporta o XML, o programa processa como dados estruturados e
|
|
devolve um XML para importar. Nada é patcheado, nenhuma API privada é usada.
|
|
|
|
São **quatro camadas**, e o ponto importante é que existem **duas portas de
|
|
entrada diferentes** para o mesmo motor:
|
|
|
|
```
|
|
┌──────────────────────────┐ ┌──────────────────────────────┐
|
|
│ MacApp/ (SwiftUI) │ │ Cliente MCP (Claude) │
|
|
│ O app que o usuário usa │ │ Conversa, decide a edição │
|
|
└───────────┬──────────────┘ └───────────────┬──────────────┘
|
|
│ subprocesso + JSON-lines │ JSON-RPC (stdio)
|
|
▼ ▼
|
|
┌──────────────────────────┐ ┌──────────────────────────────┐
|
|
│ admin/models_api.py │ │ server.py + server_tools/ │
|
|
│ + admin/api/ │ │ 77 tools, dispatch, schemas │
|
|
│ 37 comandos da ponte │ │ NÃO tem lógica de timeline │
|
|
└───────────┬──────────────┘ └───────────────┬──────────────┘
|
|
└───────────────┬────────────────────┘
|
|
▼
|
|
┌───────────────────────────────────┐
|
|
│ fcpxml/ — O ENGINE │
|
|
│ Núcleo puro Python, desacoplado. │
|
|
│ Não conhece MCP nem o app. │
|
|
│ É onde quase tudo mora. │
|
|
└───────────────────────────────────┘
|
|
```
|
|
|
|
**A regra que sustenta tudo:** nem `server.py` nem `admin/api/` implementam
|
|
lógica de timeline. Os dois validam entrada, chamam o engine e formatam a
|
|
saída. Toda regra de negócio é testável sem MCP e sem app.
|
|
|
|
**Por que duas portas.** O MCP existe para o julgamento editorial — qual tomada
|
|
usar, onde dar zoom — que é conversa com uma IA. A ponte existe para o que o
|
|
usuário faz sozinho no app — transcrever, configurar, processar. As duas caem
|
|
no mesmo engine, então uma correção ali vale para as duas.
|
|
|
|
---
|
|
|
|
## 2. Regras transversais (valem em todo o código)
|
|
|
|
| Conceito | Regra |
|
|
|----------|-------|
|
|
| **Tempo** | `TimeValue`, fração racional `"600/2400s"`. **Nunca float para tempo.** |
|
|
| **Tempo de decisão** | Ações de voz usam sempre segundos da **mídia original**, nunca pós-corte. |
|
|
| **I/O paths** | Sempre via `_validate_filepath` / `_validate_output_path` (sandbox). |
|
|
| **Nome de saída** | Nunca sobrescrever o original: `generate_output_path()` gera `_suffix`. |
|
|
| **Segurança XML** | Sempre `defusedxml` via `safe_xml.py`. Nunca `xml.etree` direto para ler. |
|
|
| **Deps opcionais** | `librosa`/`ffmpeg`/`huggingface_hub` importados **lazy**, degradam com `None`. |
|
|
| **Idioma** | Comunicação com o usuário em português. Código e comentários em inglês. |
|
|
| **Validação** | `./Engine/run_after_fix.sh` **sempre** após cada correção. |
|
|
| **App** | Alterou `MacApp/`? Compile e rode: `admin/run_app.command` (padrão de revisão; equivale a `./MacApp/build_app.sh --run`). |
|
|
|
|
---
|
|
|
|
## 3. Fluxo de um request
|
|
|
|
### Pela porta MCP (Claude decidindo a edição)
|
|
|
|
```
|
|
Cliente MCP ──JSON-RPC──► server.py
|
|
│ TOOL_HANDLERS[nome]
|
|
▼
|
|
server_tools/<categoria>.py
|
|
│ _shared/: valida path, parseia projeto
|
|
▼
|
|
fcpxml/ (parser → writer → safe_xml)
|
|
▼
|
|
projeto_<suffix>.fcpxml (original intocado)
|
|
```
|
|
|
|
### Pela porta do app (usuário operando)
|
|
|
|
```
|
|
MacApp ──Process + argv JSON──► admin/models_api.py
|
|
│ handlers[comando]
|
|
▼
|
|
admin/api/<assunto>.py
|
|
│ shared.emit() devolve JSON-lines
|
|
▼
|
|
fcpxml/ (ou chama um handler do server)
|
|
▼
|
|
arquivo gerado + caminho de volta ao app
|
|
```
|
|
|
|
A saída da ponte é **JSON-lines**: um documento JSON por linha, para que
|
|
comandos longos transmitam progresso enquanto rodam. Toda escrita passa por
|
|
`admin/api/shared.py::emit`, que serializa o acesso a stdout — dois comandos
|
|
escrevendo ao mesmo tempo entrelaçariam documentos.
|
|
|
|
---
|
|
|
|
## 4. Dual-mode: XML + Live
|
|
|
|
- **Modo XML (principal):** exporta FCPXML, processa como dados, reimporta.
|
|
- **Modo Live (`fcpxml/live.py`):** *push* do FCPXML direto para o FCP em
|
|
execução via Apple events oficiais (`Open Document`). Leitura de bibliotecas
|
|
via AppleScript read-only.
|
|
|
|
**Assimetria estrutural:** import é scriptable, mas a Apple não oferece export
|
|
programático. Round-trips sempre voltam pelas ferramentas XML.
|
|
|
|
---
|
|
|
|
## 5. Onde está cada responsabilidade
|
|
|
|
| Responsabilidade | Fica em |
|
|
|------------------|---------|
|
|
| Modelos de dados (tempo, clips, markers, QC, legendas) | `fcpxml/models/` |
|
|
| Parse FCPXML → objetos | `fcpxml/parser.py` |
|
|
| Edição e escrita de FCPXML | `fcpxml/writer/` |
|
|
| Geração de timeline nova | `fcpxml/rough_cut.py` |
|
|
| Comparação de timelines | `fcpxml/diff.py` |
|
|
| Export cross-NLE (Resolve, FCP7) | `fcpxml/export.py` |
|
|
| Silêncio e beats | `fcpxml/media_intel.py` |
|
|
| Transcrição Whisper | `fcpxml/transcribe.py` |
|
|
| Diarização (quem falou) | `fcpxml/diarize.py` |
|
|
| Ênfase acústica | `fcpxml/emphasis.py`, `fcpxml/voice_features.py` |
|
|
| Timeline de voz (o JSON que a IA lê) | `fcpxml/voice_timeline.py` |
|
|
| Decisões de edição (cut/zoom/text/marker) | `fcpxml/voice_actions.py` |
|
|
| Revisão de frases da etapa 5 | `fcpxml/phrase_review.py` |
|
|
| Layout de legendas e métricas de fonte | `fcpxml/text_layout.py`, `font_metrics.py`, `collision.py` |
|
|
| Gestão de modelos Whisper | `fcpxml/model_manager.py` |
|
|
| Controle Live do FCP | `fcpxml/live.py` |
|
|
| Segurança XML | `fcpxml/safe_xml.py` |
|
|
| Validação contra DTDs da Apple | `fcpxml/dtd.py` |
|
|
| Transporte MCP (77 tools) | `server.py` + `server_tools/` |
|
|
| Ponte com o app (37 comandos) | `admin/models_api.py` + `admin/api/` |
|
|
| Interface do usuário | `MacApp/Sources/` |
|
|
|
|
---
|
|
|
|
## 6. Mapa de dependências
|
|
|
|
```
|
|
MacApp/ ──► admin/models_api.py (subprocesso, por caminho)
|
|
admin/api/ ──► fcpxml/* e, para algumas operações, server.py
|
|
server.py ──► server_tools/*
|
|
server_tools/* ──► server_tools/_shared/ ──► fcpxml/*
|
|
fcpxml/writer/ ──► fcpxml/models/, safe_xml, dtd, text_layout, collision
|
|
fcpxml/models/ ──► fcpxml/text_layout (só o pacote subtitles)
|
|
fcpxml/__init__.py ──► reexporta a API pública
|
|
```
|
|
|
|
**A seta que não existe, e não deve existir:** `fcpxml/` nunca importa de
|
|
`server_tools/`, de `admin/` ou de qualquer coisa que saiba o que é uma tool.
|
|
Se você precisar disso, a lógica está no lugar errado.
|
|
|
|
---
|
|
|
|
## 7. Criando algo novo — por onde começar
|
|
|
|
| Você quer… | Comece por |
|
|
|-----------|-----------|
|
|
| Uma **ferramenta MCP** nova | Função pura em `fcpxml/` + teste. O handler em `server_tools/` fica fino. |
|
|
| Um **comando do app** novo | Mesmo caminho, e exponha em `admin/api/<assunto>.py` + tabela em `models_api.py`. |
|
|
| Uma **tela** nova | `MacApp/Sources/`, consumindo comandos que já existem na ponte. |
|
|
| Uma **regra de edição** nova | `fcpxml/` sempre. Se você está escrevendo `if` sobre timeline fora de `fcpxml/`, pare. |
|
|
|
|
O trabalho principal é **sempre** no engine. As camadas de cima são finas de
|
|
propósito: é o que permite testar 1.498 casos sem abrir o app nem subir o MCP.
|