Files
gart/code/Engine/docs/03_SERVER_TOOLS.md
T

7.8 KiB
Raw Blame History

03 — Camada MCP (server.py) — 73 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).

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

As 73 ferramentas por categoria

Timeline & análise (Projeto)

list_projects, analyze_timeline, list_clips, list_markers, list_connected_clips, list_compound_clips, list_library_clips, list_roles, list_keywords, list_effects.

QC e detecção

find_short_cuts, find_long_clips, analyze_pacing, detect_flash_frames, detect_duplicates, detect_gaps, validate_timeline, detect_silence_candidates, detect_media_silence, remove_silence_candidates, remove_media_silence, detect_beats.

Edição

add_marker, batch_add_markers, trim_clip, reorder_clips, add_transition, change_speed, delete_clips, split_clip, insert_clip, fix_flash_frames, rapid_trim, fill_gaps, add_audio, create_compound_clip, flatten_compound_clip.

Geração

auto_rough_cut, generate_montage, generate_ab_roll, list_templates, apply_template.

Beats / markers importados

import_beat_markers, snap_to_beats, import_srt_markers, import_transcript_markers.

Roles

assign_role, filter_by_role, export_role_stems.

Transcrição & edição por transcrição

transcribe_media, edit_by_transcript, remove_filler_words.

Diferenciação

diff_timelines.

export_edl, export_csv, export_resolve_xml, export_fcp7_xml, relink_media.

Reformat

reformat_timeline.

Voz (análise → decisão → aplicação)

analyze_voice_features, build_voice_timeline, refine_voice_timeline, remove_speakers, apply_voice_actions, get_voice_analysis_config, save_voice_analysis_config.

O fluxo é sempre o mesmo: build_voice_timeline mede (caro, roda uma vez) → o modelo decide os cortes → refine_voice_timeline renormaliza sobre o que sobrou (barato, sem reabrir áudio) e propõe as janelas de zoom → o modelo corta a lista pelo ritmo → apply_voice_actions aplica. Pular a renormalização faz o ranking de ênfase apontar para as palavras erradas (ver 05_EXPERIENCIAS.md).

Legendas dinâmicas (geração → validação → aplicação)

generate_dynamic_subtitles, validate_subtitle_layout, transcript_markers.

Sempre gere e depois valide — nunca dê a geração como pronta sem validate_subtitle_layout. A composição garante "sem sobreposição" só por construção dentro do que ela mesma sabe medir; um título editado à mão, uma palavra fora do alcance do que foi calibrado, ou conteúdo antigo no mesmo arquivo escapam dessa garantia. Fluxo:

generate_dynamic_subtitles(filepath)
        ↓
validate_subtitle_layout(output_path)   ← sempre, mesmo quando "parece certo"
        ↓
severidade none/warning → entregar
severidade probable/severe → investigar CADA colisão pela fração exata do
XML antes de mudar código (ver checklist abaixo)

Antes de atribuir uma colisão ao gerador, confirme que é o gerador. Um <title> de nome estranho (ref diferente, params tipo Auto-Shrink/ Left Margin que _make_text_title_clip nunca escreve) é conteúdo humano ou de outra ferramenta, não um bug — comparar contra o arquivo original (grep pelo texto) resolve em segundos. Caso real: uma colisão "severa" era um título manual feito no FCP que sobrou no arquivo reaproveitado como base de teste (05_EXPERIENCIAS.md, 2026-08-19).

Antes de atribuir uma colisão a uma sobreposição real, confirme pela fração exata do FCPXML, não pelo float arredondado. Dois títulos que só se tocam na borda (um bloco some exatamente quando o próximo começa, por design) podem imprimir tempos "iguais" e ainda assim colidir no relatório por ruído de ponto flutuante — float(a+b) != float(c) mesmo quando as frações a+b e c são idênticas. temporal_overlap() já tem uma tolerância (_BOUNDARY_EPSILON = 1e-6, muitas ordens abaixo de um frame) para absorver isso; se uma colisão nova parecer nascer do nada, comparar m._parse_time(...) dos dois títulos por igualdade exata antes de suspeitar de sobreposição de verdade.

Constantes que resolvem os três bugs já encontrados nesta área (todas em fcpxml/text_layout.py, exceto a última):

Constante O que resolve Por quê
TEXT_TEMPLATE_FONT_SCALE = 2.0 Posição e tamanho de fonte dessincronizados O template "Text" do FCP posiciona no espaço do frame (2160×3840), mas o layout mede em pontos de meia-escala (1080×1920). Escalar só o tamanho da fonte e não a posição espalha o texto errado — os dois têm que ser convertidos pelo mesmo fator na saída.
_EMPHASIS_ITALIC_CUSHION_RATIO = 0.06 Linha de corpo lendo apertada sob a linha de ênfase O itálico da Playfair inclina as hastes além da caixa de tinta que a métrica mede; ~14pt de respiro extra só nessa fronteira corrige sem tocar no line_gap do resto.
fit_emphasis() (função, não constante) Palavra de ênfase estourando o frame inteiro Só linhas de corpo faziam wrap contra box.width; a linha de ênfase (sempre uma palavra só) nunca foi checada. Uma palavra longa ou toda maiúscula podia medir mais que o frame inteiro sozinha. Encolhe font_size+kerning pelo mesmo fator até caber — nunca abaixo do tamanho do corpo, senão ênfase deixa de ser ênfase.
_BOUNDARY_EPSILON = 1e-6 (fcpxml/collision.py) Falso positivo de colisão em títulos que só se tocam Ver parágrafo acima.

Detalhe de implementação e efeito medido de cada um: 05_EXPERIENCIAS.md, entradas de 2026-08-19 (#15 zoom, #16 colisão por float, #17 auto-fit da ênfase — a #15 é do módulo de voz, não de legendas, mas mesma causa-raiz de fundo: um mecanismo que lê a própria saída anterior precisa continuar sendo fonte de verdade legível, não só efeito colateral write-only).

Live (macOS)

push_to_fcp, list_fcp_libraries.


Padrão de handler (a forma de fazer)

async def handle_<nome>(arguments: dict):
    tree, timeline, project = _parse_project(arguments)   # 1. parseia
    # ...opera com o Engine (parser/writer/rough_cut/export)...
    return _text_result(text)                             # 2. devolve

Regras:

  • Todo handler valida caminho com _validate_filepath/_validate_output_path.
  • Saídas sempre com sufixo _modified, _chapters, etc. — original nunca é tocado.
  • Cada handler tem o seu async def handle_<name>(arguments: dict).
  • 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_<nome> em server.py seguindo o padrão acima.
  3. Registrar no dicionário TOOL_HANDLERS.
  4. Rodar ./Engine/run_after_fix.sh.