From dcdd73edb505e11ce8220679cd5be385f75d6708 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Henrique?= Date: Wed, 19 Aug 2026 22:51:33 -0400 Subject: [PATCH] =?UTF-8?q?docs:=20varredura=20geral,=20documenta=C3=A7?= =?UTF-8?q?=C3=A3o=20por=20fun=C3=A7=C3=A3o=20e=20regra=20de=20atualiza?= =?UTF-8?q?=C3=A7=C3=A3o?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A documentação descrevia um sistema que não existe mais: 62/73 ferramentas (são 74), writer.py e models.py como arquivos (viraram pacotes), 1032 testes (são 1454), models_api.py descrito como "API FastAPI" (é ponte JSON) e o app SwiftUI ausente por completo — 5.500 linhas que o usuário opera todo dia sem uma linha de documentação. Cada arquivo passa a ter uma função específica, com cabeçalho de escopo dizendo o que cobre e o que NÃO cobre (com a seta para quem cobre). O objetivo é ler só o necessário: doc fora do assunto custa tempo e processamento sem entregar nada. 01 arquitetura camadas, duas portas de entrada, regras transversais 02 módulos mapa do engine, incluindo o pipeline de voz 03 server/tools as 74 tools, helpers e como criar uma nova 08 app macOS NOVO — build por swiftc, telas, ponte, etapa 5 09 manutenção NOVO — por onde começar, o que está aberto, sintoma→arquivo CLAUDE.md ganha a seção "Documentação (MANDATORY)": tabela de roteamento (qual arquivo abrir para cada tarefa) e a regra de que toda alteração de código atualiza a doc no mesmo commit, com o mapa de o-que-mexeu → o-que- atualizar. Doc velha engana mais que doc ausente. O índice do 05_EXPERIENCIAS subiu para o topo: consultar "isso já quebrou antes?" custava carregar 1.281 linhas antes de chegar na tabela. Dívidas levantadas na varredura e registradas em 09 §2: etapa 6 ainda ignora o phrase_review.json, offset de ~400ms do Whisper, MacApp sem teste, admin/ fora do lint, confirmações visuais pendentes no FCP, submódulo WHISPERX sujo. Também corrigidos dois links quebrados no Engine/README que apontavam um nível acima do certo. Co-Authored-By: Claude Opus 5 --- CLAUDE.md | 92 ++++++-- code/Engine/README.md | 75 +++--- code/Engine/docs/01_ARCHITECTURE.md | 204 ++++++++++------ code/Engine/docs/02_MODULES.md | 269 ++++++++++++++-------- code/Engine/docs/03_SERVER_TOOLS.md | 76 ++++-- code/Engine/docs/04_TESTS_AND_WORKFLOW.md | 3 + code/Engine/docs/05_EXPERIENCIAS.md | 69 +++--- code/Engine/docs/06_BOAS_PRATICAS.md | 3 + code/Engine/docs/08_APP_MACOS.md | 195 ++++++++++++++++ code/Engine/docs/09_MANUTENCAO.md | 156 +++++++++++++ 10 files changed, 881 insertions(+), 261 deletions(-) create mode 100644 code/Engine/docs/08_APP_MACOS.md create mode 100644 code/Engine/docs/09_MANUTENCAO.md diff --git a/CLAUDE.md b/CLAUDE.md index b3e91e7..6c9e379 100755 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -9,25 +9,91 @@ normalmente; a regra é sobre a comunicação com o usuário. ## What This Is -MCP server that reads/writes Final Cut Pro XML (FCPXML) files. 73 tools for timeline analysis, batch editing, QC, generation, multi-track support, media relink, NLE export, transcript-based editing (local Whisper), and LIVE FCP control (push_to_fcp / list_fcp_libraries via Apple events). Reads FCPXML 1.8–1.14 (incl. `.fcpxmld` bundles with sidecar preservation), writes 1.13 by default. Dual-mode (XML + Live) direction: `code/docs/CAPABILITY-AUDIT-2026-06.md`. +MCP server that reads/writes Final Cut Pro XML (FCPXML) files. 74 tools for timeline analysis, batch editing, QC, generation, multi-track support, media relink, NLE export, transcript-based editing (local Whisper), and LIVE FCP control (push_to_fcp / list_fcp_libraries via Apple events). Reads FCPXML 1.8–1.14 (incl. `.fcpxmld` bundles with sidecar preservation), writes 1.13 by default. Dual-mode (XML + Live) direction: `code/docs/CAPABILITY-AUDIT-2026-06.md`. ## Architecture Toda a estrutura do projeto fica em `code/`. A pasta `admin/` fica na raiz (fora de `code/`). -``` -code/server.py — MCP server entry point. All 62 tool definitions, handlers, resources, prompts. - Dispatch dict pattern: TOOL_HANDLERS maps tool names → async handler functions. +Há **duas portas de entrada** para o mesmo engine: o MCP (Claude decide a +edição) e a ponte JSON (o app macOS opera). Nenhuma das duas tem lógica de +timeline — as duas delegam a `fcpxml/`. -code/fcpxml/parser.py — Reads FCPXML → Python objects (Timeline, Clip, ConnectedClip, Marker, etc.) -code/fcpxml/writer.py — Writes modifications back to FCPXML. Handles markers, trimming, gaps, transitions. -code/fcpxml/rough_cut.py — Generates new timelines from source clips (rough cuts, montages, A/B rolls). -code/fcpxml/diff.py — Timeline comparison engine. Detects added/removed/moved/trimmed clips & markers. -code/fcpxml/export.py — DaVinci Resolve FCPXML v1.9 export + FCP7 XMEML v5 export for cross-NLE workflows. -code/fcpxml/models.py — Data classes: TimeValue, Timecode, Clip, ConnectedClip, CompoundClip, Timeline, etc. -code/fcpxml/media_intel.py — Real media analysis. Audio silence detection + beat detection. -code/fcpxml/dtd.py — Validates output against Apple's official DTDs. ``` +code/server.py — MCP entry point (592 linhas). Só dispatch: TOOL_HANDLERS. +code/server_tools/ — Os handlers das 74 tools, um módulo por categoria. +code/server_tools/_shared/ — Helpers compartilhados (paths, project, formatting, + captions, detection, media). + +code/fcpxml/parser.py — Reads FCPXML → Python objects (Timeline, Clip, Marker…) +code/fcpxml/writer/ — PACOTE. Edição/escrita de FCPXML. FCPXMLModifier é + montado por mixins, um por assunto (markers, trim, + speed, titles, cut, silence…). Ver writer/modifier.py. +code/fcpxml/models/ — PACOTE. Data classes por família: timing, timeline, + enums, subtitles, qc, planning. +code/fcpxml/rough_cut.py — Generates new timelines (rough cuts, montages, A/B rolls). +code/fcpxml/diff.py — Timeline comparison engine. +code/fcpxml/export.py — DaVinci Resolve v1.9 + FCP7 XMEML v5 export. +code/fcpxml/media_intel.py — Silence detection + beat detection. +code/fcpxml/dtd.py — Validates output against Apple's official DTDs. +code/fcpxml/voice_*.py — Pipeline de voz: features → emphasis → voice_timeline + → voice_actions → phrase_review. Ver Engine/docs/02. + +admin/models_api.py — Ponte JSON com o app: docstring de comandos + dispatch. +admin/api/ — Os 37 comandos, um módulo por assunto. +code/MacApp/Sources/ — App SwiftUI. Compilado por swiftc (sem Xcode/SPM). +``` + +Os dois `__init__.py` de pacote (`writer/`, `models/`) reexportam tudo, então +`from .writer import FCPXMLModifier` e `from .models import TimeValue` seguem +valendo em todo o projeto. + +## Documentação (MANDATORY) + +A documentação viva fica em `code/Engine/docs/`. Cada arquivo tem **uma função +específica** — leia só o que a tarefa exige, não o conjunto. Carregar +documentação que não é do assunto custa tempo e processamento sem entregar nada. + +### Qual arquivo abrir + +| Sua tarefa | Abra | Não precisa de | +|-----------|------|----------------| +| Entender como o sistema é dividido | `01_ARCHITECTURE.md` | o resto | +| Achar onde mora uma função do engine | `02_MODULES.md` | 01, 03 | +| Criar/alterar uma ferramenta MCP | `03_SERVER_TOOLS.md` | 08 | +| Entender ou rodar os testes | `04_TESTS_AND_WORKFLOW.md` | — | +| "Isso já quebrou antes?" | `05_EXPERIENCIAS.md` — **só o índice no topo** | as entradas que não são a sua | +| Checklist antes de fechar | `06_BOAS_PRATICAS.md` | — | +| Mexer no app / no Assistente | `08_APP_MACOS.md` | 02, 03 | +| Escolher o que fazer, ver o que está aberto | `09_MANUTENCAO.md` | — | + +Quando não souber por onde começar: `09_MANUTENCAO.md`. Ele roteia para o resto. + +### Regra de atualização (obrigatória) + +**Toda alteração de código atualiza a documentação no mesmo commit.** Doc velha +engana mais do que doc ausente — quem lê confia nela e erra com confiança. + +| Você alterou | Atualize | +|--------------|----------| +| Estrutura de pastas, camadas ou dependências | `01_ARCHITECTURE.md` | +| Criou/moveu/dividiu módulo em `fcpxml/` | `02_MODULES.md` (tabela + linhas) | +| Criou/removeu ferramenta MCP | `03_SERVER_TOOLS.md` + contagem no `CLAUDE.md` | +| Comando da ponte | docstring de `admin/models_api.py` + `08_APP_MACOS.md` | +| Tela ou fluxo do app | `08_APP_MACOS.md` | +| Resolveu ou abriu uma dívida | `09_MANUTENCAO.md` §2 | +| Bateu num problema estrutural ou erro recorrente | `05_EXPERIENCIAS.md` + **índice no topo** | + +Se um número (tools, testes, linhas) mudou, corrija onde ele aparece. Se um +documento divergir do código, **o código está certo** — conserte o documento. + +### Ao escrever documentação + +- **Um assunto por arquivo.** Se um doc começar a cobrir dois, divida. +- **Diga o que não está ali** e para onde ir — economiza a leitura seguinte. +- **Fatos verificados**, não suposições: rode o comando e use o número real. +- **Registre o porquê**, não só o quê. O "o quê" está no código; o "por quê" + se perde, e é o que evita alguém desfazer uma decisão por engano. ## Key Patterns @@ -88,7 +154,7 @@ CI runs both on every push to main. If either fails, the commit gets an X on Git ## Testing -1342 tests across 34 files. `test_models.py` covers TimeValue arithmetic, Timecode parsing/formatting, Clip properties, validation models, and Timeline helpers. `test_writer.py` covers insert_clip, add_marker (all types), trim_clip, delete_clip, split_clip, and change_speed operations. `test_server.py` covers MCP tool handlers, parsers, and dispatch. `test_rough_cut.py` covers RoughCutGenerator. `test_features_v05.py` covers connected clips, roles, timeline diff, reformat, silence detection, export, and backward compatibility. `test_marker_pipeline.py` covers build_marker_element shared builder, batch auto-modes, clip index duplicate-name behavior, and write_fcpxml output format. `test_refactored_helpers.py` covers _index_elements, _iter_spine_clips, _find_spine_clip_at_seconds, _resolve_clip_duration, _make_asset_clip, _format_batch_result, and serialize_xml edge cases. `test_transcribe.py` covers phrase/filler span matching, range merge/invert algebra, whisper graceful degradation, and transcript-driven handler cuts against cached transcripts. `test_media_intel.py` covers silencedetect stderr parsing, source-to-timeline mapping, parameter bounds, and real-WAV ffmpeg integration (skips without ffmpeg; CI installs it). Tests use `examples/sample.fcpxml` as fixture data and inline XML fixtures. Tests create temp files and clean up after. +1454 tests across 42 files, all under `code/tests/`. Um teste fora dessa pasta não roda (`testpaths = ["tests"]`) — se você criar um em outro lugar, confirme que a contagem total subiu. Cobertura por área: `test_models.py` (TimeValue/Timecode/Clip/Timeline), `test_writer.py` (insert/marker/trim/delete/split/speed), `test_server.py` (handlers e dispatch), `test_rough_cut.py`, `test_features_v05.py` (connected clips, roles, diff, reformat, silêncio, export), `test_marker_pipeline.py`, `test_refactored_helpers.py`, `test_transcribe.py`, `test_media_intel.py` (pula sem ffmpeg; o CI instala), `test_phrase_review.py` (revisão de frases da etapa 5) e `test_models_api.py` (comandos da ponte). Fixtures: `examples/sample.fcpxml` e XML inline. Os testes criam temporários e limpam depois. ## FCPXML Gotchas diff --git a/code/Engine/README.md b/code/Engine/README.md index 6ee99a7..15ce56f 100644 --- a/code/Engine/README.md +++ b/code/Engine/README.md @@ -10,12 +10,20 @@ opera **fora** do Final Cut Pro: você exporta o XML, o servidor processa o documento como dados estruturados e devolve um XML modificado para importação. Nada é patcheado, nenhuma API privada é usada. -Toda a análise foi feita a partir do código-fonte em `server.py` e `fcpxml/`. +Toda a análise foi feita a partir do código-fonte. Este README é a visão +geral; o detalhe módulo a módulo mora em `docs/02_MODULES.md`, que é o +documento a manter atualizado quando a estrutura mudar. -> **Guia rápido:** [01 Arquitetura](docs/01_ARCHITECTURE.md) · +> **Começando agora?** Leia [01 Arquitetura](docs/01_ARCHITECTURE.md) e depois +> [09 Manutenção](docs/09_MANUTENCAO.md) — o primeiro diz como o sistema é +> dividido, o segundo diz por onde começar a mexer e o que está em aberto. +> +> **Guia completo:** [01 Arquitetura](docs/01_ARCHITECTURE.md) · > [02 Módulos](docs/02_MODULES.md) · [03 Server/Tools](docs/03_SERVER_TOOLS.md) · > [04 Testes & Workflow](docs/04_TESTS_AND_WORKFLOW.md) · -> [05 Experiências](docs/05_EXPERIENCIAS.md) · [06 Boas Práticas](docs/06_BOAS_PRATICAS.md) +> [05 Experiências](docs/05_EXPERIENCIAS.md) · [06 Boas Práticas](docs/06_BOAS_PRATICAS.md) · +> [07 Projeto Ativo no FCP](docs/07_ESTUDO_PROJETO_ATIVO_FCP.md) · +> [08 App macOS](docs/08_APP_MACOS.md) · [09 Manutenção](docs/09_MANUTENCAO.md) --- @@ -26,7 +34,7 @@ Toda a análise foi feita a partir do código-fonte em `server.py` e `fcpxml/`. Python, e reescreve de volta sem perda de sidecars (object tracking, Cinematic). -2. **Uma camada MCP de 62 ferramentas** — expõe análise, edição em lote, QC, +2. **Uma camada MCP de 74 ferramentas** — expõe análise, edição em lote, QC, geração, exportação cross-NLE, inteligência de mídia (silêncio/beats) e edição baseada em transcrição, tudo acessível por um cliente MCP (Claude). @@ -40,7 +48,7 @@ Toda a análise foi feita a partir do código-fonte em `server.py` e `fcpxml/`. | Camada | Tecnologia | |--------|-----------| -| Linguagem | **Python 3.10+** (~7.1k linhas em `server.py` + `fcpxml/`) | +| Linguagem | **Python 3.10+** (~13k linhas em `server.py`, `server_tools/` e `fcpxml/`) | | Protocolo MCP | **mcp** (`mcp` SDK), servidor por stdio | | Parsing XML | **defusedxml** em todos os 4 entry points + `lxml`/`ElementTree` | | Tempo racional | frações `numerador/denominador` no formato `"600/2400s"` | @@ -56,26 +64,29 @@ Toda a análise foi feita a partir do código-fonte em `server.py` e `fcpxml/`. ``` G-ART/ -├── server.py # MCP server — 62 tools, prompts, resources, dispatch -├── fcpxml/ # "Engine" — biblioteca Python de núcleo -│ ├── models.py # TimeValue, Timecode, Clip, Timeline, enums, QC models -│ ├── parser.py # FCPXML → objetos Python (spine, connected clips, roles) -│ ├── writer.py # Modifica e grava FCPXML (markers, trim, gaps, speed) -│ ├── rough_cut.py # Gera timelines novas (rough cuts, montages, A/B) -│ ├── diff.py # Motor de comparação de timelines -│ ├── export.py # Export DaVinci Resolve v1.9 + FCP7 XMEML v5 -│ ├── media_intel.py # Detecção real de silêncio (ffmpeg) e beats (librosa) -│ ├── transcribe.py # Transcrição Whisper local + edição por transcrição -│ ├── templates.py # Templates de timeline (intro/outro, lower thirds) -│ ├── live.py # Modo Live — push_to_fcp / list_fcp_libraries -│ ├── safe_xml.py # Wrappers defusedxml + serialize_xml() -│ └── dtd.py # Validação contra DTDs oficiais da Apple -├── Engine/ # Esta documentação da arquitetura -├── admin/ # Scripts de manutenção (graphify.sh, graphify.md) -├── docs/ # WORKFLOWS, CAPABILITY-AUDIT, specs -├── examples/ # Fixture de teste (sample.fcpxml) -├── tests/ # 1032 testes / 24 suítes -└── tools/ # Pacote Python (__init__) +├── CLAUDE.md # Regras do projeto para o agente +├── admin/ # Ponte com o app (fora de code/) +│ ├── models_api.py # Entry point: docstring dos comandos + dispatch +│ └── api/ # Os 37 comandos, um módulo por assunto +└── code/ + ├── server.py # MCP entry point — só dispatch + ├── server_tools/ # Handlers das 74 tools + _shared/ + ├── fcpxml/ # "Engine" — biblioteca Python de núcleo + │ ├── writer/ # PACOTE: edição/escrita (mixins por assunto) + │ ├── models/ # PACOTE: dados por família (timing, timeline…) + │ ├── parser.py # FCPXML → objetos Python + │ ├── rough_cut.py # Gera timelines novas + │ ├── voice_*.py # Pipeline de voz (features → timeline → actions) + │ ├── phrase_review.py # Revisão de frases da etapa 5 + │ ├── text_layout.py # Diagramação das legendas + │ ├── live.py # Modo Live — push_to_fcp + │ ├── safe_xml.py # defusedxml + serialize_xml() + │ └── dtd.py # Validação contra DTDs da Apple + ├── MacApp/Sources/ # App SwiftUI (compilado por swiftc) + ├── Engine/ # Esta documentação + ├── docs/ # WORKFLOWS, CAPABILITY-AUDIT, specs + ├── examples/ # Fixture de teste (sample.fcpxml) + └── tests/ # 1.454 testes / 42 suítes ``` --- @@ -102,7 +113,7 @@ TimeValue(600, 2400) # "600/2400s" == 0.25s - Soma/subtração compartilham um único caminho `_binop()` (fast-path de mesmo denominador + alinhamento por LCM). -### 4.2 Modelos principais — `models.py` +### 4.2 Modelos principais — `models/` | Classe | Função | |--------|--------| @@ -126,8 +137,8 @@ escrita. `from_xml_element` faz match estrito do atributo `completed` | Subsistema | Módulo | Função | |-----------|--------|--------| | Parser | `parser.py` | FCPXML → objetos Python: espinha, connected clips, secondary storylines, roles | -| Modifier | `writer.FCPXMLModifier` | Edição index-based (clips/resources/formats dicts) do documento existente | -| Writer | `writer.FCPXMLWriter` | Gera FCPXML novo a partir de objetos Python | +| Modifier | `writer/` (`FCPXMLModifier`) | Edição index-based (clips/resources/formats dicts) do documento existente | +| Writer | `writer/generator.py` | Gera FCPXML novo a partir de objetos Python | | Rough cut | `rough_cut.py` | Gera timelines (rough cuts, montages, A/B roll) | | Diff | `diff.py` | Compara timelines — detecta added/removed/moved/trimmed | | Export | `export.py` | DaVinci Resolve v1.9 + FCP7 XMEML v5 | @@ -151,7 +162,7 @@ assíncrono: TOOL_HANDLERS = { "analyze_timeline": handle_analyze_timeline, "list_clips": handle_list_clips, - # ... 62 tools + # ... 74 tools, todos em server_tools/ } ``` @@ -249,7 +260,7 @@ correção, sem depender de lembrar dos dois comandos no pre-commit. - [docs/02_MODULES.md](docs/02_MODULES.md) — guia módulo a módulo do `fcpxml/` (responsabilidade, tamanho, APIs públicas). - [docs/03_SERVER_TOOLS.md](docs/03_SERVER_TOOLS.md) — a camada MCP `server.py`, - 62 ferramentas, helpers e o padrão de handler. + 74 ferramentas, helpers e o padrão de handler. - [docs/04_TESTS_AND_WORKFLOW.md](docs/04_TESTS_AND_WORKFLOW.md) — suíte de testes, fluxo de trabalho (lint + pytest), execução e estado atual do sistema. - [docs/05_EXPERIENCIAS.md](docs/05_EXPERIENCIAS.md) — **memória de projeto**: @@ -259,10 +270,10 @@ correção, sem depender de lembrar dos dois comandos no pre-commit. programação** a aplicar em toda alteração/correção; inclui checklist final. ### Outros documentos -- [../CLAUDE.md](../CLAUDE.md) — visão geral, key patterns, execução e pre-commit. +- [../CLAUDE.md](../../CLAUDE.md) — visão geral, key patterns, execução e pre-commit. - [../docs/CAPABILITY-AUDIT-2026-06.md](../docs/CAPABILITY-AUDIT-2026-06.md) — auditoria do ecossistema e roadmap dual-mode (XML + Live). - [../docs/WORKFLOWS.md](../docs/WORKFLOWS.md) — 8 receitas de workflow de produção. - [../docs/specs/](../docs/specs/) — schemas de tools, estrutura FCPXML, pseudocódigo do writer, algoritmo de rough cut, implementação do server, roadmap, modelos. -- [../admin/graphify.md](../admin/graphify.md) — pipeline de graphify do código. +- [../admin/graphify.md](../../admin/graphify.md) — pipeline de graphify do código. diff --git a/code/Engine/docs/01_ARCHITECTURE.md b/code/Engine/docs/01_ARCHITECTURE.md index 02522b8..11fd3d0 100644 --- a/code/Engine/docs/01_ARCHITECTURE.md +++ b/code/Engine/docs/01_ARCHITECTURE.md @@ -1,109 +1,177 @@ # 01 — Arquitetura do Sistema (G-ART / fcp-mcp-server) -> Referência canônica de como o sistema está dividido e implementado. Leia este -> documento antes de qualquer mudança de código. +> **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 · 74 ferramentas MCP · 1.454 testes · versão `0.6.35` + +--- ## 1. Visão de cima (camadas) -O sistema é um **servidor MCP em Python** que lê/analisa/reescreve arquivos -**FCPXML** do Final Cut Pro. Há **três camadas** bem separadas: +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: ``` -┌─────────────────────────────────────────────────────────────┐ -│ admin/ — Aplicações complementares (fora do MCP) │ -│ models_api.py API (FastAPI) p/ gerenciar modelos │ -│ models_gui.py UI desktop (Flet) p/ gerenciar modelos │ -│ graphify.sh/.md Pipeline de graphify do código │ -├─────────────────────────────────────────────────────────────┤ -│ server.py — CAMADA MCP / TRANSPORTE (NÃO tem lógica) │ -│ 73 tools, handlers, prompts, resources, dispatch │ -│ Só valida entrada/saída e traduz JSON-RPC → chamadas │ -├─────────────────────────────────────────────────────────────┤ -│ fcpxml/ — "ENGINE" = NÚCLEO PURO Python (desacoplado) │ -│ Não conhece MCP nem argumentos de tool. │ -│ Trabalha com objetos Python e XML. │ -│ É o foco / onde quase tudo mora. │ -└─────────────────────────────────────────────────────────────┘ +┌──────────────────────────┐ ┌──────────────────────────────┐ +│ 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/ │ │ 74 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. │ + └───────────────────────────────────┘ ``` -**Regra de arquitetura:** `server.py` NUNCA implementa lógica de timeline — -ele delega ao `fcpxml/`. Tudo em `fcpxml/` é testável isoladamente (1032 testes). +**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. -## 2. Regras transversais (convenções em todo o código) +**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 use float p/ tempo. | -| **I/O paths** | Sempre via helpers `_validate_filepath` / `_validate_output_path` (sandbox). | -| **Nome de saída** | Nunca sobrescrever original: `output_.fcpxml`. | -| **Segurança XML** | Sempre `defusedxml` (via `safe_xml.py`). Nunca `xml.etree` direto. | +| **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`. | -| **Lint** | `ruff check . --exclude docs/` — zero erros. | -| **Validação pós-correção** | `./Engine/run_after_fix.sh` SEMPRE após cada correção. | +| **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: `./MacApp/build_app.sh --run`. | -## 3. Fluxo de um request (round-trip) +--- + +## 3. Fluxo de um request + +### Pela porta MCP (Claude decidindo a edição) ``` -Cliente MCP (Claude) - │ JSON-RPC (stdio) - ▼ -server.py ── dispatcher (TOOL_HANDLERS) - │ valida path, parseia projeto, chama engine - ▼ -fcpxml/parser.py XML → objetos -fcpxml/writer.py edita / grava -fcpxml/rough_cut.py gera novas timelines -fcpxml/export.py cross-NLE - ▼ -output_.fcpxml (original intocado) - ▼ -Final Cut Pro: File → Import → XML (ou push_to_fcp, sem cliques) +Cliente MCP ──JSON-RPC──► server.py + │ TOOL_HANDLERS[nome] + ▼ + server_tools/.py + │ _shared/: valida path, parseia projeto + ▼ + fcpxml/ (parser → writer → safe_xml) + ▼ + projeto_.fcpxml (original intocado) ``` +### Pela porta do app (usuário operando) + +``` +MacApp ──Process + argv JSON──► admin/models_api.py + │ handlers[comando] + ▼ + admin/api/.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 -O sistema opera em **dois modos complementares**: - - **Modo XML (principal):** exporta FCPXML, processa como dados, reimporta. - Roda fora do FCP. Nenhuma API privada. -- **Modo Live (`fcpxml/live.py`):** *push* do FCPXML direto p/ o FCP em - execução via Apple events oficiais (`Open Document`), com `import-options`. - Leitura de bibliotecas via AppleScript read-only. +- **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 voltam pelas ferramentas XML. +programático. Round-trips sempre voltam pelas ferramentas XML. + +--- ## 5. Onde está cada responsabilidade | Responsabilidade | Fica em | |------------------|---------| -| Modelos de dados (tempo, clips, markers) | `fcpxml/models.py` | +| Modelos de dados (tempo, clips, markers, QC, legendas) | `fcpxml/models/` | | Parse FCPXML → objetos | `fcpxml/parser.py` | -| Editing/escrita (modifier + writer) | `fcpxml/writer.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` | -| Inteligência de mídia (silêncio/beats) | `fcpxml/media_intel.py` | -| Transcrição Whisper local | `fcpxml/transcribe.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` | -| Templates de timeline | `fcpxml/templates.py` | | Controle Live do FCP | `fcpxml/live.py` | -| Segurança XML (`defusedxml`, `serialize_xml`) | `fcpxml/safe_xml.py` | +| Segurança XML | `fcpxml/safe_xml.py` | | Validação contra DTDs da Apple | `fcpxml/dtd.py` | -| Transporte MCP (73 tools) | `server.py` | +| Transporte MCP (74 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 (você está aqui se for mexer no X → quem tocar) +--- + +## 6. Mapa de dependências ``` -server.py ──► fcpxml/parser, writer, rough_cut, export, diff, - media_intel, transcribe, templates, live, dtd -admin/models_gui.py ──► fcpxml/media_intel, model_manager, - parser, transcribe -admin/models_api.py ──► fcpxml/model_manager -fcpxml/writer.py ──► fcpxml/models, safe_xml, dtd -fcpxml/__init__.py ──► reexporta a API pública +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 ``` -> Se você cria uma **nova ferramenta MCP**, o trabalho principal é em `fcpxml/` -> (função pura + testes). O handler em `server.py` fica fino: validação de -> caminho → `_parse_project` → chama a função → `_text_result`. \ No newline at end of file +**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/.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.454 casos sem abrir o app nem subir o MCP. diff --git a/code/Engine/docs/02_MODULES.md b/code/Engine/docs/02_MODULES.md index e610370..50217d4 100644 --- a/code/Engine/docs/02_MODULES.md +++ b/code/Engine/docs/02_MODULES.md @@ -1,114 +1,191 @@ # 02 — Módulos do Engine (`fcpxml/`) -Guia módulo a módulo do núcleo Python. Tamanho em linhas, responsabilidade e as -funções/classes públicas de cada um. APIs públicas são reexportadas em -`fcpxml/__init__.py` (fonte da verdade para o `__all__`). +> **Escopo:** Mapa do engine `fcpxml/`: qual módulo faz o quê e onde mexer. +> **Não cobre:** Camadas e regras gerais (→ 01) · handlers MCP (→ 03) · o que está aberto (→ 09) -## Versão atual -`__version__ = "0.6.35"` — ver `fcpxml/__init__.py`. +Mapa módulo a módulo do núcleo Python: onde cada coisa mora e o que ela faz. +A API pública é reexportada em `fcpxml/__init__.py` — essa é a fonte da verdade +do `__all__`. + +Versão: `0.6.35` · Última varredura: 2026-08-19 + +> **Por que existem pacotes aqui.** `writer.py` tinha 4.199 linhas e `models.py` +> 1.091, cada um com muitos assuntos dentro. Viraram pacotes com um módulo por +> assunto. Do lado de fora **nada mudou**: `from .writer import FCPXMLModifier` +> e `from .models import TimeValue` seguem valendo, porque os `__init__.py` +> reexportam tudo — inclusive os nomes com underscore que a suíte usa. --- -| Módulo | Linhas | Papel | -|--------|-------:|-------| -| `models.py` | 930 | Data classes e enums (tempo, clips, markers, QC) | -| `parser.py` | 367 | FCPXML → objetos Python | -| `writer.py` | 3154 | Edição e escrita de FCPXML (o maior) | +## Visão geral + +| Módulo / pacote | Linhas | Papel | +|-----------------|-------:|-------| +| `writer/` | 4.687 | **Edição e escrita de FCPXML** — o coração | +| `models/` | 1.195 | Data classes e enums | +| `text_layout.py` | 901 | Diagramação das legendas dinâmicas | | `rough_cut.py` | 798 | Geração de timelines novas | -| `dtd.py` | 112 | Validação contra DTDs oficiais | -| `safe_xml.py` | 113 | Wrappers `defusedxml` + `serialize_xml()` | -| `media_intel.py` | 173 | Silêncio (ffmpeg) e beats (librosa) | -| `transcribe.py` | 184 | Transcrição Whisper + edição por transcrição | -| `model_manager.py` | 298 | Gestão de modelos Whisper (cache/catálogo) | -| `export.py` | 226 | Export DaVinci Resolve v1.9 + FCP7 XMEML v5 | -| `diff.py` | 269 | Comparação de timelines | -| `live.py` | 273 | Modo Live — push_to_fcp / list_fcp_libraries | +| `model_manager.py` | 748 | Modelos Whisper: catálogo, download, config | +| `voice_timeline.py` | 594 | O JSON de voz que a IA lê | +| `phrase_review.py` | 547 | Revisão de frases (etapa 5 do assistente) | +| `collision.py` | 472 | Colisão entre títulos na tela | +| `font_metrics.py` | 445 | Largura real de glifos por fonte | | `templates.py` | 387 | Templates de timeline | -| `__init__.py` | 139 | Reexporta API pública | +| `parser.py` | 367 | FCPXML → objetos Python | +| `transcribe.py` | 300 | Transcrição Whisper e corte por texto | +| `live.py` | 273 | Modo Live (push_to_fcp) | +| `diff.py` | 269 | Comparação de timelines | +| `voice_actions.py` | 263 | Decisões de edição (cut/zoom/text/marker) | +| `export.py` | 226 | Export Resolve v1.9 + FCP7 XMEML v5 | +| `voice_features.py` | 220 | Pitch, energia, ritmo, pausas | +| `diarize.py` | 180 | Quem falou (pyannote) | +| `media_intel.py` | 177 | Silêncio (ffmpeg) e beats (librosa) | +| `emphasis.py` | 133 | Índice de ênfase por palavra | +| `safe_xml.py` | 113 | `defusedxml` + `serialize_xml()` | +| `dtd.py` | 112 | Validação contra os DTDs da Apple | --- -## `models.py` — modelos e enums -Single source of truth para estrutura de dados. NUNCA mexa aqui sem rodar -`test_models.py`. +## `writer/` — edição e escrita -- **Tempo:** `TimeValue` (fração racional), `Timecode`. -- **Clips:** `Clip`, `VideoClip`, `AudioClip`, `ConnectedClip` (lane), - `CompoundClip`, `Transition`. -- **Contêineres:** `Timeline`, `Project`, `Keyword`. -- **Markers:** `Marker`, `MarkerType`, `MarkerColor`, `MARKER_XML_TAGS`. - `MarkerType` é o dono da serialização (`from_string`/`from_xml_element`/`xml_attrs`). - Match estrito do atributo `completed` (`'0'`/`'1'`, sem padding). -- **QC:** `SilenceCandidate`, `FlashFrame`, `GapInfo`, `DuplicateGroup`, - `ValidationIssue`, `ValidationResult`. -- **Geração:** `SegmentSpec`, `PacingConfig`, `PacingStyle`, `RoughCutResult`. +O `FCPXMLModifier` é montado por **composição de mixins**: um mixin por assunto +editorial, todos operando sobre o mesmo documento e os mesmos índices. -## `parser.py` — leitura -- `parse_fcpxml(path)` → `Project`. -- `FCPXMLParser` — lê spine, connected clips (lanes), secondary storylines, roles. +| Módulo | Linhas | Conteúdo | +|--------|-------:|----------| +| `core.py` | 723 | `ModifierCore`: carga, índices, navegação na spine, `save` | +| `titles.py` | 600 | Títulos de texto e legendas dinâmicas | +| `cut.py` | 333 | Dividir, cortar faixas, apagar | +| `speed.py` | 297 | Velocidade e zoom (punch-in) | +| `helpers.py` | 279 | Sanitização, escalas, construtores de elemento | +| `rapid.py` | 240 | Flash frames, rapid trim, preencher buracos | +| `validation.py` | 232 | Verificações estruturais antes de salvar | +| `compound.py` | 196 | Compound clips: criar e achatar | +| `silence.py` | 185 | Detectar e remover silêncio | +| `document.py` | 170 | Assets de vídeo, timebases, `write_fcpxml` | +| `markers.py` | 165 | Marcadores: um, por timecode, em lote | +| `audio.py` | 162 | Clipes de áudio e cama musical | +| `generator.py` | 147 | `FCPXMLWriter` — cria documento do zero | +| `reorder.py` | 126 | Reordenar e recalcular offsets | +| `trim.py` | 125 | Aparar e propagar o ripple | +| `transitions.py` | 94 | Transições entre vizinhos | +| `relink.py` | 94 | Repontar mídia | +| `insert.py` | 78 | Inserir clipes na spine | +| `modifier.py` | 64 | Monta a classe a partir dos mixins | +| `selection.py` | 57 | Selecionar por palavra-chave | +| `api.py` | 55 | Atalhos de uma linha | +| `connected.py` | 49 | Clipes conectados (lanes) | +| `roles.py` | 43 | Atribuir roles | +| `reformat.py` | 43 | Reenquadrar resolução | -## `writer.py` — o coração (3154 linhas) -Duas classes principais: +**Onde mexer:** ache o assunto na tabela e abra só aquele arquivo. Se a sua +mudança precisa de dois mixins ao mesmo tempo, provavelmente o que você quer +é um método novo no `core.py` que os dois chamem. -- **`FCPXMLModifier`** — edita documento existente de forma index-based - (dicts de `clips`/`resources`/`formats`), imune a ambiguidade de nomes duplicados. - Métodos: `insert_clip`, `add_marker`, `trim_clip`, `delete_clip`, `split_clip`, - `change_speed`, `cut_clip_ranges` (usado pela remoção de silêncio), etc. -- **`FCPXMLWriter`** — gera FCPXML novo a partir de objetos Python. - -Helpers de nível de arquivo: `modify_fcpxml`, `add_marker_to_file`, -`trim_clip_in_file`, `build_marker_element`, `write_fcpxml`, `validate_fcpxml`, -`list_effects`, `FCP_EFFECTS`. - -## `rough_cut.py` — geração -- `RoughCutGenerator`, `generate_rough_cut`, `generate_segmented_rough_cut`. - -## `media_intel.py` — inteligência de mídia (v0.10) -- Silêncio via `ffmpeg silencedetect` (subprocess limitado), `remove_silence_candidates`, - mapeamento source→timeline. -- Beats via `librosa` (import lazy, extra `[intelligence]`). -- Degrada para `None` quando `ffmpeg` ausente. - -## `transcribe.py` — Whisper local -- `transcribe(media_path, model_size, language)` → dict com `words` (spans). -- `ALLOWED_MODELS` — allowlist de nomes de modelo (também usado por `model_manager`). -- Edição por transcrição: remove filler words, aparar por transcrição. - -## `model_manager.py` — gestão de modelos -Catálogo `models.json` + cache no HF hub. Config em `~/.fcp-mcp-server/config.json`. -Funções: `get/save_models_dir`, `list_installed_models`, `download_model`, -`delete_model`, `get/load_selected_model`, `save_selected_model`, `load_catalog`. -Permite cancelamento de download via `threading.Event`. Segue convenções: -allowlist, lazy imports, degradação graciosa. - -## `export.py` — cross-NLE -- `DaVinciExporter` — FCPXML v1.9 p/ DaVinci Resolve. -- Export FCP7 XMEML v5. - -## `diff.py` — comparação -- `compare_timelines`, `TimelineDiff`, `ClipDiff`, `MarkerDiff`. -- Detecta added/removed/moved/trimmed clips & markers. - -## `live.py` — FCP ao vivo (macOS) -- `push_to_fcp(path, library, options)` — Apple event *Open Document* + ``. - Requer `.fcpbundle` p/ zero-click real. -- `list_fcp_libraries()` — AppleScript read-only. - -## `templates.py` -- `Template`, `TemplateSlot`, `ClipSpec`, `BUILTIN_TEMPLATES`, `apply_template`, - `list_templates`. Estruturas prontas: intro/outro, lower thirds, music video. - -## `safe_xml.py` -Wrappers `defusedxml` centralizados + `serialize_xml()`. Todo parse/escrita passa aqui. - -## `dtd.py` -Valida output contra DTDs oficiais no bundle do FCP (via `xmllint`; exige o caminho -do DTD percent-encoded por causa dos espaços em "Final Cut Pro.app"). +**Cuidado:** os mixins compartilham `self`. Um método novo que colida de nome +com outro mixin sobrescreve em silêncio — a ordem em `modifier.py` decide quem +ganha. Hoje nenhum colide; mantenha assim. --- -## Como adicionar um módulo novo -1. Criar `fcpxml/.py` — função pura, sem conhecer MCP. -2. Reexportar classes/funções em `fcpxml/__init__.py` (`__all__`). -3. Cobrir em `tests/test_.py`. -4. Rodar `./Engine/run_after_fix.sh`. \ No newline at end of file +## `models/` — dados e enums + +Fonte única da estrutura de dados. **Nunca mexa aqui sem rodar `test_models.py`.** + +| Módulo | Linhas | Conteúdo | +|--------|-------:|----------| +| `timing.py` | 304 | `TimeValue` (fração racional), `Timecode` | +| `timeline.py` | 217 | `Clip`, `ConnectedClip`, `CompoundClip`, `Timeline`, `Project`, `Marker` | +| `enums.py` | 183 | `MarkerType`, `MarkerColor`, `TransitionType`, `PacingStyle`… | +| `subtitles.py` | 157 | `WordLook`, `WordStyle`, `DynamicSubtitleConfig`, paleta | +| `qc.py` | 121 | `FlashFrame`, `GapInfo`, `DuplicateGroup`, `ValidationIssue` | +| `planning.py` | 93 | `SegmentSpec`, `PacingConfig`, `RoughCutResult`, `MontageConfig` | + +`MarkerType` é o dono da serialização de marcador (`from_string`, +`from_xml_element`, `xml_attrs`) — não reimplemente isso em outro lugar. + +--- + +## O caminho da voz (do áudio à decisão) + +Estes seis módulos formam um pipeline. É o fluxo mais novo e o menos óbvio do +projeto, então vale ler nesta ordem: + +``` +transcribe.py áudio → palavras com tempo + + +diarize.py quem falou cada trecho + + +voice_features.py pitch, energia, ritmo, pausas + ▼ +emphasis.py combina tudo num índice 0–1 por palavra + ▼ +voice_timeline.py monta o _voice_timeline.json ◄── é isto que a IA lê + ▼ + [decisão: skill "editar-por-voz", ou a mão do usuário] + ▼ +voice_actions.py valida a lista de cut/zoom/text/marker + ▼ +phrase_review.py funde tudo em frases revisáveis (etapa 5 do app) + ▼ +writer/ aplica no FCPXML +``` + +**Regra de ouro do pipeline:** toda ação carrega tempo da **mídia original**, +nunca pós-corte. Cortes deslocam tudo depois deles; resolver o deslocamento só +na hora de aplicar (`shift_after_cuts`) elimina uma classe inteira de bug. + +### `voice_timeline.py` — o contrato com a IA + +Saída em camadas, para um modelo raciocinar do topo e descer só onde importa: + +``` +{version, source, language, + layers: {transcript, acoustics, speakers, emotion} ← o que rodou de verdade + scales: {…} ← como ler cada número + summary: {…} + speakers: [...] + segments: [{start, end, speaker, text, gap_before, take_boundary, + avg_energy, peak_emphasis, emotion, emotion_confidence, + words: [{text, start, end, energy, pitch_delta, rate_delta, + pause_before, emphasis}]}]} +``` + +`layers` existe para separar *"a fala é monótona"* de *"a análise acústica nunca +carregou"* — os dois deixam os mesmos zeros nos dados. + +### `phrase_review.py` — a revisão humana + +Junta o timeline de voz com as ações da IA numa lista de frases editáveis, e +converte de volta. Frase inativa vira `cut`; ênfase ≥ 1 vira `zoom` mais um +`emphasis_spans` que a etapa de legendas usa. O trim de cada frase anda em +**fronteira de palavra** — cortar é apontar para uma palavra, nunca caçar frame. + +--- + +## Legendas dinâmicas (três módulos que andam juntos) + +| Módulo | Papel | +|--------|-------| +| `text_layout.py` | Quebra a frase em linhas e posiciona cada palavra | +| `font_metrics.py` | Largura real de cada glifo na fonte escolhida | +| `collision.py` | Detecta título saindo do quadro ou colidindo com outro | + +Estes três não estão divididos porque **cada um já é um assunto só**. O +`text_layout.py` tem 901 linhas de um problema coeso: diagramação. + +--- + +## Armadilhas do FCPXML (custaram sessões de depuração) + +- Tempo é fração: `"3600/2400s"` = 1,5 s. +- `offset` é posição na timeline; `start` é o in-point da mídia. +- `` (biblioteca) é diferente de `` (timeline). +- Marcadores são **filhos** do clipe, não irmãos. +- `.fcpxmld` é um **diretório** — sidecars precisam ser copiados no save, ou + dados de object tracking e Cinematic são destruídos. +- Negrito no FCP é `bold="1"` (atributo); itálico é `fontFace` + `italic="1"`. +- `id` de `` precisa ser XML Name válido — acento, espaço ou + dígito inicial fazem o FCP recusar o arquivo inteiro. +- `code/examples/sample.fcpxml` **não** é DTD-conformante. Não use como fixture + de validade. diff --git a/code/Engine/docs/03_SERVER_TOOLS.md b/code/Engine/docs/03_SERVER_TOOLS.md index 472a4d8..cb14e6e 100644 --- a/code/Engine/docs/03_SERVER_TOOLS.md +++ b/code/Engine/docs/03_SERVER_TOOLS.md @@ -1,26 +1,49 @@ -# 03 — Camada MCP (`server.py`) — 73 ferramentas +# 03 — Camada MCP (`server.py` + `server_tools/`) — 74 ferramentas -`server.py` (3824 linhas) é a camada de transporte. Não tem lógica de timeline — -mapeia nome → handler e delega ao Engine. O dispatch é um dicionário -`TOOL_HANDLERS` (padrão de despacho, sem cadeias gigantes de if/elif). +> **Escopo:** As 74 ferramentas MCP: helpers, categorias e como criar uma nova. +> **Não cobre:** Lógica de edição, que mora no engine (→ 02) · comandos do app (→ 08) + +`server.py` (592 linhas) é só o transporte: dispatch por dicionário +`TOOL_HANDLERS`, sem cadeia de if/elif e **sem lógica de timeline**. Os handlers +moram em `server_tools/`, um módulo por categoria, e os helpers que todos usam +em `server_tools/_shared/`. + +``` +server_tools/ + editing.py (649) qc.py (696) voice.py (754) timeline.py (400) + subtitles.py markers_import generation.py transcript.py + export.py roles.py live.py + _shared/ ← helpers compartilhados, ver abaixo +``` ## Helpers centrais (use-os, não reinvente) -| Helper | Linha | Função | -|--------|------:|--------| -| `_check_json_depth()` | 83 | Rejeita payloads além de 50 níveis | -| `_validate_filepath()` | 103 | Sandbox de entrada | -| `_validate_output_path()` | 149 | Sandbox de saída | -| `_format_clip_table()` | 245 | Renderização de tabela | -| `_markdown_table()` | 259 | Renderização de tabela markdown | -| `_parse_project()` | 319 | Parseia FCPXML → `(tree, timeline, project)`; quase todos os handlers começam aqui | -| `_resolve_io_paths()` | 357 | Validação de caminho de entrada/saída | -| `_setup_modifier()` | 390 | Prepara modifier com validação | -| `_setup_generator()` | 414 | Prepara generator com validação | -| `_parse_timestamp_parts()` | 433 | Parse de timestamps (min:seg, H:MM:SS, SMPTE) | -| `_detect_flash_frames/gaps/duplicate_groups()` | 1667+ | Detectores de QC | +Todos reexportados por `server_tools/_shared`, então `from ._shared import X` +continua funcionando. A coluna diz o módulo real, para quando você precisar +**editar** o helper — ou apontar um `monkeypatch` para ele. -## As 73 ferramentas por categoria +| Helper | Mora em | Função | +|--------|---------|--------| +| `_validate_filepath()` | `_shared/paths.py` | Sandbox de entrada | +| `_validate_output_path()` | `_shared/paths.py` | Sandbox de saída | +| `_check_json_depth()` | `_shared/paths.py` | Rejeita payloads além de 50 níveis | +| `generate_output_path()` | `_shared/paths.py` | Nome derivado, sem tocar no original | +| `_resolve_io_paths()` | `_shared/paths.py` | Entrada + saída de uma vez | +| `_parse_project()` | `_shared/project.py` | FCPXML → `(tree, timeline, project)`; quase todo handler começa aqui | +| `_setup_modifier()` | `_shared/project.py` | Prepara modifier já validado | +| `_setup_generator()` | `_shared/project.py` | Prepara generator já validado | +| `_text_result()` | `_shared/project.py` | Envolve o texto em `TextContent` MCP | +| `_markdown_table()` | `_shared/formatting.py` | Tabela markdown | +| `_format_clip_table()` | `_shared/formatting.py` | Tabela de clipes | +| `_format_batch_result()` | `_shared/formatting.py` | Relatório de operação em lote | +| `_parse_timestamp_parts()` | `_shared/captions.py` | min:seg, H:MM:SS, SMPTE | +| `parse_srt()` / `parse_vtt()` | `_shared/captions.py` | Legendas coladas | +| `_detect_flash_frames/gaps/duplicate_groups()` | `_shared/detection.py` | Detectores de QC | +| `_load_or_transcribe()` | `_shared/media.py` | Transcrição com cache em disco | +| `_cut_transcript_spans()` | `_shared/media.py` | Corte por trecho falado | +| `_apply_placed_action()` | `_shared/media.py` | Aplica zoom/text/marker já posicionado | + +## As 74 ferramentas por categoria ### Timeline & análise (Projeto) `list_projects`, `analyze_timeline`, `list_clips`, `list_markers`, `list_connected_clips`, @@ -144,7 +167,18 @@ Regras: - Sempre retornam via `_text_result(text)` (envolve o texto em `TextContent` MCP). ## Para adicionar uma ferramenta nova -1. Escrever a função no módulo do Engine (`fcpxml/…`) + testes. -2. Criar `handle_` em `server.py` seguindo o padrão acima. -3. Registrar no dicionário `TOOL_HANDLERS`. + +1. **Escrever a função no Engine** (`fcpxml/…`) com testes. É aqui que mora o + trabalho de verdade; o resto é encanamento. +2. **Criar `handle_`** em `server_tools/.py`, seguindo o padrão + acima. Escolha a categoria pelo assunto, não pelo tamanho do arquivo. +3. **Declarar o schema** (`Tool(...)`) no mesmo módulo. +4. **Registrar** no `TOOL_HANDLERS` de `server.py`. +5. Rodar `./Engine/run_after_fix.sh`. + +Se a ferramenta também deve aparecer no app, exponha um comando equivalente em +`admin/api/.py` e registre na tabela de `admin/models_api.py` — ver +`08_APP_MACOS.md`. Uma capacidade que só existe como tool MCP **não existe para +quem usa o app** (foi exatamente o que aconteceu com `apply_voice_actions`, +`05_EXPERIENCIAS.md` #20). 4. Rodar `./Engine/run_after_fix.sh`. \ No newline at end of file diff --git a/code/Engine/docs/04_TESTS_AND_WORKFLOW.md b/code/Engine/docs/04_TESTS_AND_WORKFLOW.md index 6264b6f..1ba27e5 100644 --- a/code/Engine/docs/04_TESTS_AND_WORKFLOW.md +++ b/code/Engine/docs/04_TESTS_AND_WORKFLOW.md @@ -1,5 +1,8 @@ # 04 — Testes, Fluxo de Trabalho e Estado Atual +> **Escopo:** Como rodar e escrever testes, e o gate antes de commitar. +> **Não cobre:** O que testar em cada módulo (→ 02) · checklist de qualidade (→ 06) + ## 1. Suíte de testes **1032 testes em 24 arquivos** em `tests/`. Rode com `uv run pytest tests/ -v`. diff --git a/code/Engine/docs/05_EXPERIENCIAS.md b/code/Engine/docs/05_EXPERIENCIAS.md index ef988a7..bf3b166 100644 --- a/code/Engine/docs/05_EXPERIENCIAS.md +++ b/code/Engine/docs/05_EXPERIENCIAS.md @@ -11,6 +11,44 @@ houver uma correção ou trabalho em torno dele, **adicione um registro aqui** antes de prosseguir. Um problema que se repete em várias tentativas é sinal de que merece entrada. + +> **Como usar:** o índice abaixo é o ponto de entrada. Procure o sintoma +> aqui primeiro; só abra a entrada completa (mais abaixo) se ela for a sua. +> As entradas ficam em ordem cronológica depois do índice. + +## Resumo rápido (índice) + +| # | Data | Problema | Estado | +|---|------|----------|--------| +| 1 | 2026-08-14 | Início do registro de experiências | `resolvido` | +| 4 | 2026-08-14 | XML fora da grade de frame em NTSC (23.976/29.97fps), confirmado no FCP | `resolvido` | +| 5 | 2026-08-14 | `TimeValue.from_timecode` corrompia segundos decimais em NTSC (3º ponto do bug) | `resolvido` | +| 6 | 2026-08-17 | Clipe-fantasma de 1 frame no início/fim após remoção de silêncio (padding sem vizinho na borda) | `resolvido` | +| 7 | 2026-08-17 | Legendas dinâmicas sobrepondo entre clipes (título conectado não é aparado pelo out-point do pai) | `resolvido` | +| 8 | 2026-08-17 | Importação recusada: `id` de `` derivado do texto (acentos/espaços/dígito inicial) não é XML Name válido | `resolvido` | +| 9 | 2026-08-17 | Legendas palavra a palavra centradas em vez da composição progressiva diagramada (bloco por trecho, palavra-chave em display italic) | `resolvido` | +| 10 | 2026-08-17 | Cedilha/acentos da display italic invadindo a linha vizinha: empilhamento passou a usar a tinta real por classe de glifo | `resolvido` | +| 11 | 2026-08-18 | Preview das legendas dinâmicas desproporcional ao render do FCP (stagger/gap/canvas divergentes) e `inactive_color` exposto sem efeito | `resolvido` | +| 12 | 2026-08-18 | Espaço de coordenadas do modelo "Text": `fontSize`, `kerning` e `Position` no espaço do quadro — converter só o tamanho descolou o espaçamento | `resolvido` | +| 13 | 2026-08-19 | Reanálise de ênfase implementada no Engine mas sem ferramenta MCP — Fase 4 da skill era inexecutável | `resolvido` | +| 14 | 2026-08-19 | Offset sistemático de ~0,4s no timing por palavra (faster-whisper sem alinhamento forçado) — corrigido manualmente no teste, WhisperX pendente | `parcialmente resolvido` | +| 15 | 2026-08-19 | `add_zoom` perdia o enquadramento real (voltava a 100%) quando dois zooms caiam no mesmo clipe pós-corte; agora empilha ou substitui conforme as janelas se sobrepõem | `resolvido` | +| 16 | 2026-08-19 | `validate_subtitle_layout` acusava colisão severa em títulos que só se tocam na borda, por não-associatividade de float; 7 de 8 colisões reportadas no teste real eram falso positivo | `resolvido` | +| 17 | 2026-08-19 | Linha de ênfase das legendas dinâmicas sem limite de largura — palavra longa/maiúscula estourava o frame inteiro; auto-fit encolhe até caber, nunca abaixo do corpo | `resolvido` | +| 18 | 2026-08-19 | Legendas dinâmicas geradas com `bold="0" fontFace="Bold"` não renderizam no FCP — negrito deve ser `bold="1"` (atributo) e itálico `fontFace`+`italic="1"` | `resolvido` | +| 19 | 2026-08-19 | `output_dir` usado só como cerca de validação e nunca como destino — toda chamada entre pastas falhava acusando o caminho que ela mesma gerou | `resolvido` | +| 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` | +| 24 | 2026-08-19 | `admin/test_models_api.py` existia mas estava fora de `testpaths` — 13 testes que nunca rodaram | `resolvido` | + +> Mantenha o índice acima sempre sincronizado com as entradas mais recentes. + +--- + +## Entradas (ordem cronológica) + --- ## Como registrar (template de entrada) @@ -1275,34 +1313,3 @@ o outro; percentil entrega um punhado útil nos dois casos. rodando. Vale também para o lint: `admin/` ainda não é coberto pelo `run_after_fix.sh`, que roda só dentro de `code/`. - **Estado:** `resolvido` - ---- - -## Resumo rápido (índice) - -| # | Data | Problema | Estado | -|---|------|----------|--------| -| 1 | 2026-08-14 | Início do registro de experiências | `resolvido` | -| 4 | 2026-08-14 | XML fora da grade de frame em NTSC (23.976/29.97fps), confirmado no FCP | `resolvido` | -| 5 | 2026-08-14 | `TimeValue.from_timecode` corrompia segundos decimais em NTSC (3º ponto do bug) | `resolvido` | -| 6 | 2026-08-17 | Clipe-fantasma de 1 frame no início/fim após remoção de silêncio (padding sem vizinho na borda) | `resolvido` | -| 7 | 2026-08-17 | Legendas dinâmicas sobrepondo entre clipes (título conectado não é aparado pelo out-point do pai) | `resolvido` | -| 8 | 2026-08-17 | Importação recusada: `id` de `` derivado do texto (acentos/espaços/dígito inicial) não é XML Name válido | `resolvido` | -| 9 | 2026-08-17 | Legendas palavra a palavra centradas em vez da composição progressiva diagramada (bloco por trecho, palavra-chave em display italic) | `resolvido` | -| 10 | 2026-08-17 | Cedilha/acentos da display italic invadindo a linha vizinha: empilhamento passou a usar a tinta real por classe de glifo | `resolvido` | -| 11 | 2026-08-18 | Preview das legendas dinâmicas desproporcional ao render do FCP (stagger/gap/canvas divergentes) e `inactive_color` exposto sem efeito | `resolvido` | -| 12 | 2026-08-18 | Espaço de coordenadas do modelo "Text": `fontSize`, `kerning` e `Position` no espaço do quadro — converter só o tamanho descolou o espaçamento | `resolvido` | -| 13 | 2026-08-19 | Reanálise de ênfase implementada no Engine mas sem ferramenta MCP — Fase 4 da skill era inexecutável | `resolvido` | -| 14 | 2026-08-19 | Offset sistemático de ~0,4s no timing por palavra (faster-whisper sem alinhamento forçado) — corrigido manualmente no teste, WhisperX pendente | `parcialmente resolvido` | -| 15 | 2026-08-19 | `add_zoom` perdia o enquadramento real (voltava a 100%) quando dois zooms caiam no mesmo clipe pós-corte; agora empilha ou substitui conforme as janelas se sobrepõem | `resolvido` | -| 16 | 2026-08-19 | `validate_subtitle_layout` acusava colisão severa em títulos que só se tocam na borda, por não-associatividade de float; 7 de 8 colisões reportadas no teste real eram falso positivo | `resolvido` | -| 17 | 2026-08-19 | Linha de ênfase das legendas dinâmicas sem limite de largura — palavra longa/maiúscula estourava o frame inteiro; auto-fit encolhe até caber, nunca abaixo do corpo | `resolvido` | -| 18 | 2026-08-19 | Legendas dinâmicas geradas com `bold="0" fontFace="Bold"` não renderizam no FCP — negrito deve ser `bold="1"` (atributo) e itálico `fontFace`+`italic="1"` | `resolvido` | -| 19 | 2026-08-19 | `output_dir` usado só como cerca de validação e nunca como destino — toda chamada entre pastas falhava acusando o caminho que ela mesma gerou | `resolvido` | -| 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` | -| 24 | 2026-08-19 | `admin/test_models_api.py` existia mas estava fora de `testpaths` — 13 testes que nunca rodaram | `resolvido` | - -> Mantenha o índice acima sempre sincronizado com as entradas mais recentes. diff --git a/code/Engine/docs/06_BOAS_PRATICAS.md b/code/Engine/docs/06_BOAS_PRATICAS.md index 35318a0..6231b2e 100644 --- a/code/Engine/docs/06_BOAS_PRATICAS.md +++ b/code/Engine/docs/06_BOAS_PRATICAS.md @@ -1,5 +1,8 @@ # 06 — Boas Práticas de Programação (G-ART) +> **Escopo:** Checklist de qualidade a aplicar antes de dar algo por pronto. +> **Não cobre:** Por onde começar uma tarefa (→ 09) · o que já quebrou (→ 05) + > **Propósito:** registrar as melhores práticas de programação a serem aplicadas > **sempre** que qualquer alteração ou correção for feita neste programa. > Servem de checklist obrigatório antes de concluir qualquer mudança. diff --git a/code/Engine/docs/08_APP_MACOS.md b/code/Engine/docs/08_APP_MACOS.md new file mode 100644 index 0000000..44305ee --- /dev/null +++ b/code/Engine/docs/08_APP_MACOS.md @@ -0,0 +1,195 @@ +# 08 — O app macOS (`MacApp/`) e o Assistente + +> **Escopo:** O app SwiftUI e o Assistente: build, telas, ponte e a etapa 5. +> **Não cobre:** Engine Python (→ 02) · ferramentas MCP (→ 03) + +O app SwiftUI é como o usuário opera o sistema sem abrir terminal nem conversar +com uma IA. São ~5.500 linhas em `MacApp/Sources/`, e ele **não tem lógica de +edição**: tudo que ele faz é montar argumentos, chamar a ponte Python e mostrar +o resultado. + +Última varredura: 2026-08-19 + +--- + +## 1. Como o app é construído — leia antes de mexer + +**Não existe `.xcodeproj` nem `Package.swift`.** O app é compilado invocando o +`swiftc` direto sobre `MacApp/Sources/*.swift`: + +```bash +cd code && ./MacApp/build_app.sh # compila e monta o .app +cd code && ./MacApp/build_app.sh --run # compila e abre +``` + +Consequências práticas, todas já sentidas: + +- **Arquivo novo em `Sources/` entra sozinho** no build. Não há lista de alvos. +- **Não dá para adicionar dependência SPM** sem antes migrar o build inteiro. +- **Compilar não prova que roda.** Componentes SwiftUI que embrulham classes + Objective-C podem falhar só em tempo de execução, ao abrir a tela. Foi o que + aconteceu com `VideoPlayer` (AVKit): compilava limpo e abortava ao abrir a + etapa 5 (`05_EXPERIENCIAS.md` #22). Por isso a regra: **alterou a interface, + abra a tela de fato.** + +### Testando uma tela sem navegar o app inteiro + +Um harness de vinte linhas compila os mesmos fontes com um `@main` próprio que +monta só a tela em questão. Reproduz crash de runtime em segundos: + +```bash +swiftc -parse-as-library -sdk "$(xcrun --sdk macosx --show-sdk-path)" \ + -target arm64-apple-macosx26.0 \ + MacApp/Sources/PhraseReviewView.swift MacApp/Sources/PhraseReviewModel.swift \ + MacApp/Sources/TimelineTracksView.swift MacApp/Sources/Models.swift \ + MacApp/Sources/PythonBridge.swift /tmp/HarnessMain.swift -o /tmp/harness +``` + +O `@main` do harness carrega a tela, imprime o que interessa e chama +`NSApplication.shared.terminate` — dá para afirmar "abriu e funcionou" sem +depender de screenshot. + +--- + +## 2. Estrutura das telas + +| Arquivo | Linhas | Papel | +|---------|-------:|-------| +| `WizardView.swift` | 808 | **O Assistente** — fluxo guiado de 7 etapas | +| `TranscriptionView.swift` | 843 | Transcrição avulsa e processamento em lote | +| `ModelDownloadView.swift` | 545 | Catálogo e download de modelos Whisper | +| `CaptionsView.swift` | 545 | Legendas dinâmicas: estilo + preview ao vivo | +| `TimelineTracksView.swift` | 506 | Timeline com trilhas, zoom e playhead | +| `PhraseReviewModel.swift` | 429 | Estado da etapa 5: frases, player, zooms | +| `PhraseReviewView.swift` | 413 | Etapa 5: preview + inspector de frases | +| `VoiceAnalysisView.swift` | 322 | Parâmetros do motor de ênfase | +| `ProjectView.swift` | 293 | Inspeção do `.fcpxml` | +| `Models.swift` | 274 | Espelhos Swift do JSON da ponte | +| `PythonBridge.swift` | 230 | **A ponte** — ver seção 3 | +| `SubtitlePreviewView.swift` | 218 | Preview 9:16 das legendas | +| `App.swift` | 69 | `NavigationSplitView` e as abas | + +Abas (`ActiveTab` em `App.swift`): Assistente · Projeto · Legendas · Análise de +Voz · Modelos · Sobre. As cinco últimas são "Avançado" — atalhos para operações +soltas. O Assistente é o caminho principal. + +--- + +## 3. `PythonBridge.swift` — como o app fala com o Python + +O app lança `admin/models_api.py` como **subprocesso**, passando o comando e um +JSON como `argv`, e lê **JSON-lines** no stdout. + +```swift +PythonBridge.call(command: "build_phrase_review", + arguments: ["voice_timeline": path]) { result, error in … } +``` + +Dois pontos que já causaram problema e estão resolvidos no código — não os +desfaça sem entender: + +- **`uv run` precisa rodar com cwd em `code/`.** O `uv` escolhe o ambiente pelo + diretório do processo, não pelo caminho do script. Rodar da raiz fazia o `uv` + criar um segundo `.venv` vazio e ignorar tudo que estava instalado em + `code/.venv` — librosa e pyannote instalavam com sucesso e o app insistia que + faltavam. +- **`scriptURL` procura `admin/models_api.py`** subindo diretórios a partir do + cwd, do bundle e do home. É o que faz o app funcionar tanto rodando do Xcode + quanto do `.app` montado. + +Para adicionar um comando: função em `admin/api/.py`, registro na +tabela de `admin/models_api.py`, e `PythonBridge.call` do lado Swift. Os 37 +comandos e seus formatos estão documentados no docstring de `models_api.py`. + +--- + +## 4. O Assistente — as 7 etapas + +`WizardStep` (`WizardView.swift`) é um enum sequencial; `canAdvance` decide +quando o botão "Continuar" libera. + +| # | Etapa | O que acontece | Comando da ponte | +|---|-------|----------------|------------------| +| 1 | Projeto | Escolhe a pasta de saída e o `.fcpxml` | `project_config` | +| 2 | Transcrever | Transcreve toda a mídia do projeto | `transcribe` | +| 3 | Analisar voz | Mede ênfase, locutores, emoção | `analyze_voice` | +| 4 | Decisões da IA | Copia para o chat, cola o JSON de volta, aplica | `apply_voice_actions` | +| 5 | **Revisar ênfases** | Lapida frase a frase — ver seção 5 | `build_phrase_review` / `save_phrase_review` | +| 6 | Processar | Silêncios, preenchimento, legendas | vários, em cadeia | +| 7 | Concluído | Abre no FCP ou mostra no Finder | — | + +**A etapa 4 é a única manual do fluxo**, e de propósito: o julgamento de qual +tomada usar e onde dar zoom é conversa com uma IA (skill `editar-por-voz`), não +um botão. O app monta o pedido pronto no clipboard e recebe o JSON de volta. + +**Etapa 1 — armadilha registrada:** não escolha como "o projeto" um arquivo já +gerado pelo fluxo (`_voice_edit`, `_silence_removed`, …). Os cortes de voz +assumem timestamps da mídia **original**; reaplicá-los sobre um arquivo já +cortado desloca tudo em silêncio. O wizard avisa (`looksLikeGeneratedFile`). + +--- + +## 5. Etapa 5 — a sala de edição + +Única tela que ocupa a janela toda: o corpo do wizard é uma coluna de 640pt, e +essa etapa escapa dela porque precisa da largura (`step == .revisar` em +`WizardView.body`). + +``` +┌────────────────────────────┬──────────────┐ +│ Preview (AVPlayerLayer) │ Inspector │ +│ enquadrado no formato │ de frases │ +│ de entrega do projeto │ │ +├────────────────────────────┴──────────────┤ +│ Timeline: 6 trilhas, zoom, playhead │ +└───────────────────────────────────────────┘ +``` + +**Trilhas:** zooms · frases · energia por palavra · emoção · locutor · +roteiro/bastidor. Todas desenhadas sobre o mesmo eixo de tempo, com uma coluna +fixa à esquerda nomeando cada uma. + +**O que o usuário decide por frase:** nível de ênfase (0–3), ativo/inativo, +texto, roteiro/bastidor e o trim das pontas. O trim anda em **fronteira de +palavra** — cortar é apontar para uma palavra, arrastando a borda do bloco ou +clicando na palavra no inspector. + +**Zoom manual:** arrastar na timeline marca um trecho; botão direito cria um +zoom nele. O zoom guarda **só o quando** — escala e ramp vêm das configurações +de Análise de Voz no momento do render, então mudar lá restiliza todos. + +**Decisões de implementação que parecem detalhe e não são:** + +- **O preview não renderiza nada.** Ele toca a mídia original e *pula* os + trechos removidos. Renderizar para conferir um toggle poria minutos entre a + decisão e o resultado. O observador roda a 60 Hz porque o período dele é + exatamente quanto de material cortado dá para ouvir antes do pulo. +- **O enquadramento é o do projeto, não o da mídia.** As gravações são + horizontais e a entrega é vertical; o app lê o formato do `.fcpxml` + (`inspect`) e mostra o corte central aproximado, com um selo para alternar + para a mídia original. O enquadramento real de cada clipe vem do FCP — o + preview é aproximação, e o selo diz isso. +- **Nada é processado aqui.** "Continuar" grava o `_phrase_review.json` e o + `_phrase_actions.json` derivado dele. A geração é da etapa 6. +- **A revisão é sempre remontada da análise atual**, com as decisões salvas + reaplicadas por cima (`merge_saved_decisions`). Assim refazer a análise de voz + melhora a tela em vez de ficar mascarado por uma cópia velha; uma decisão cuja + frase se moveu mais de 0,25 s é descartada em vez de colar na frase errada. + +--- + +## 6. Estado atual e o que falta + +**Funciona e foi verificado:** carga das frases com decisões da IA, as 6 +trilhas, seleção sincronizada nos três painéis, trim por palavra, zoom manual, +reprodução parando no ponto exato (erro de 0 ms medido), pulo dos trechos +removidos, enquadramento vertical, gravação ao avançar. + +**Ainda em aberto:** + +- **A etapa 6 não consome o `_phrase_review.json`.** A ligação — zoom e legenda + dinâmica só nas frases de ênfase, legenda comum no resto — é a próxima tarefa. +- **`MacApp/` não tem teste automatizado.** A rede é o harness da seção 1 e o + olho do usuário. Toda mudança de interface precisa ser aberta de fato. +- **O preview aproxima o reenquadramento vertical** pelo corte central; se os + clipes forem reposicionados no FCP, diverge. diff --git a/code/Engine/docs/09_MANUTENCAO.md b/code/Engine/docs/09_MANUTENCAO.md new file mode 100644 index 0000000..0b9aa66 --- /dev/null +++ b/code/Engine/docs/09_MANUTENCAO.md @@ -0,0 +1,156 @@ +# 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.454 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/` + 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/.py` | Registrar na tabela de `models_api.py` | +| Ferramenta MCP nova | `server_tools/.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.454 testes +cd code && ./MacApp/build_app.sh --run # se mexeu no app +``` + +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('.` 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) | +| 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 74 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