git:20261028.b3b29d6 to git:20261028.a3c506e

147 added, 135 removed. Audit A to A.

---
name: visant-premiere-editor
- description: 'Monta o bruto no Premiere Pro pelo premiere-pro-mcp a partir de um plano de corte em json: corte em quadro exato, marcador, punch-in, card e b-roll, e a entrega a -14 LUFS. Use em "monta no Premiere", "sequência do bruto", "marcadores pra narrar", "punch-in no Premiere", "põe card/aviso/logo", "cobre com b-roll", "exporta as aulas", /visant-premiere-editor.'
+ description: 'Assembles a rough cut in Premiere Pro via premiere-pro-mcp from a JSON cut plan: frame-exact cuts, markers, punch-ins, cards, b-roll, -14 LUFS delivery. Use on "build it in Premiere", "Premiere rough cut", "punch-in", "cover with b-roll", "monta no Premiere", "marcadores pra narrar", "exporta as aulas", /visant-premiere-editor.'
---
# Visant Premiere Editor
- O agente monta o bruto; o dono refina no Premiere. Nada de trilha, cor ou b-roll
- no bruto, a não ser que o dono peça.
+ ## Requirements
- Entrada: um plano de corte em json (formato no topo de `scripts/premiere_xml.py`).
- Quem gera o plano é outra etapa: decupagem e corte por fala na
- `visant-video-editor`, ou os scripts do projeto (`aula_tela.py`, `premiere_plano.py`).
- Esta skill não decide corte, só leva o plano pro Premiere sem perder quadro.
+ | Needed for | What | Check |
+ |---|---|---|
+ | everything | Windows or macOS with Adobe Premiere Pro (measured on 2026 / 26.x, PT-BR UI) | open Premiere |
+ | everything | premiere-pro-mcp with the CEP backend and the MCP Bridge panel open (Window > Extensions > MCP Bridge); measured on 1.17 | MCP tool `verify_premiere_connection` |
+ | everything | ffmpeg + ffprobe on PATH | `ffprobe -version` |
+ | `premiere_xml.py` | Python 3 + `opentimelineio` + `otio-fcp-adapter` (`pip install opentimelineio otio-fcp-adapter`) | `python -c "import opentimelineio as o; o.adapters.from_name('fcp_xml')"` |
+ | `figma_cards.py` | Python 3 (stdlib only) + Figma MCP (`use_figma`, `get_screenshot`) with skills `figma-use`, `figma-use-slides` | `python --version` |
- > **Regra zero: nunca montar corte a corte pelo MCP.** `create_subclip` +
- > `create_sequence_from_clips` erra 1 quadro por ponta (o Premiere arredonda
- > segundos pra baixo) e subclip de PNG vira 12 horas. O caminho é XML.
+ Effect names in this skill are the PT-BR UI names (`Movimento`, `Escala`, `Posição`). On another
+ locale, read them with `get_effect_properties` first.
- ## Fluxo
+ **Shipped here:** `scripts/premiere_xml.py` (plan → FCP7 XML, motion table, checker),
+ `scripts/figma_cards.py` (cards JS for Figma Slides) and `scripts/icones/` (Simple Icons, CC0).
+ **Not shipped, project-side:** the scripts named in the tighten, learn, b-roll and delivery
+ sections (`aperta_final.py`, `aprende.py`, `fala.py`, `agente.py`, `rampa.py`, `onde.py`,
+ `trecho.py`, `fonte_iphone.py`, `entrega.py`, `legenda_whisper.py`, `sensor_export.py`). They live
+ next to the project's lesson engine because they need its plan and source wavs. If the project
+ does not have them, do those steps by hand with ffmpeg and the MCP, following the same rules.
- 0. **O dono pode estar com o Premiere na mão.** `set_active_sequence` troca a
- tela dele, e o node ID de clipe muda quando a sequência ativa muda. Antes de
- cada edição, releia o ID com `get_clip_at_position`, e avise antes de uma
- rodada de mudanças.
- 1. **Conexão, só leitura.** `verify_premiere_connection` (backend `cep`) e
- `get_project_info`. Se uma chamada vencer em 120 s, tem janela aberta no
- Premiere: peça pro dono fechar e não dispare mais nada até ele fechar.
- 2. **Conferir a fonte** com `ffprobe`: resolução, fps (`r_frame_rate` e
- `avg_frame_rate`), se tem áudio, rotação. MOV de iPhone é VFR, e o Premiere
- interpreta a 29,9992.
- 3. **Gerar o XML**: `python scripts/premiere_xml.py <plano.json> --saida <dir>`.
- Precisa de `opentimelineio` e `otio-fcp-adapter` (pip).
- 4. **Importar no projeto aberto**: `import_media` com o `.xml` (`suppress_ui: true`).
- Não use `import_fcp_xml`, que cria OUTRO projeto. Uma aula por chamada.
- 5. **Religar**: a mídia do XML entra offline mesmo com o caminho certo.
- `search_project_items` com `offline_only`, depois `relink_media` de cada item
- no mesmo caminho. Até 4 em paralelo funciona.
- 6. **Punch-in** (só plano de rosto): `python scripts/premiere_xml.py --motion <plano.json>`
- dá a tabela. Aplique com `set_effect_property`, efeito `Movimento`, `Escala`
- (%) e `Posição` normalizada `[x/w, y/h]`. Em PT-BR o `set_clip_properties_batch`
- falha. Leia os nomes com `get_effect_properties` se o Premiere não for PT-BR.
- **Push-in animado** (só com aprovação do dono, mostrando antes a folha de
- quadros inicial e final): `add_keyframe` só aceita número, então a Posição
- não anima. Ponha o `Ponto de ancoragem` no rosto `[cx/w, cy/h]` e a `Posição`
- no alvo `[0.5, 0.38]`, e anime só a `Escala`. O tempo do keyframe é o da
- FONTE: use o `inPoint` do `get_clip_properties` e `inPoint + duração − 1 quadro`.
- 7. **Cor dos marcadores**: o XML não leva cor. Use `update_marker(time_seconds,
- color=1)` (vermelho) nos de plano; a fala fica no padrão (0, verde).
- 8. **Ler de volta e conferir**: `get_full_sequence_info`. Quando o resultado vai
- pra arquivo (saída grande), rode `python scripts/premiere_xml.py --conferir
- <plano.json> <arquivo>`. Quando vem inline, confira contagem de cortes, quadros
- totais, marcadores, formato e A1.
- 9. **Olhar o quadro**: `capture_frame` em 2 ou 3 pontos (um punch-in de cada
- escala e o corte mais suspeito) contra o render de referência, se houver.
- Posição encostada no limite do clamp é sinal de rosto mal detectado.
- 10. **Salvar** (`save_project`) e **arrumar**: sequências e mídias numa bin por
- entrega. A CEP não apaga item de projeto: o que for teste vai pra uma bin
- `_descartar (teste do agente)` e o nome da sequência ganha `[descartar]`,
- pro dono apagar.
+ ## Role
- ## Apertar uma sequência já editada (pausa, arrasto, caco)
+ The agent assembles the rough cut; the person editing refines it in Premiere. No music, color or
+ b-roll in the rough cut unless asked.
- Quando o dono já mexeu na sequência à mão, **não regere pelo motor**: a edição
- dele se perde. O caminho é XML de ida e volta, numa sequência nova ao lado:
+ Input: a cut plan in JSON (format at the top of `scripts/premiere_xml.py`). Another stage makes
+ the plan: speech-based breakdown and cuts in `visant-video-editor`, or the project's own scripts.
+ This skill does not decide cuts, it only carries the plan into Premiere without losing a frame.
- 1. `create_project_backup` do `.prproj`.
- 2. Pra cada aula: `set_active_sequence` (avise antes, troca a tela do dono) e
- `export_as_fcp_xml` pra `_apertada/aula-N.xml`.
- 3. `python aperta_final.py _apertada/aula-N.xml` (mora no projeto, ao lado do
- `aulas.py`, porque precisa do plano e do wav das fontes). Ele sai com
- `aula-N-apertada.xml` + `.cortes.tsv`, onde cada corte vem com o tempo, o
- motivo e a palavra.
- 4. `import_media` do XML apertado com `target_bin` (a bin do dono), mídias pra
- `<bin>/_mídia`, depois o passo 5 (religar) e o 8 (conferir). A original fica
- intacta pra comparar; reimportação leva `--sufixo v2` e não pisa na apertada
- que o dono já editou.
+ > **Rule zero: never assemble cut by cut through the MCP.** `create_subclip` +
+ > `create_sequence_from_clips` misses by 1 frame per end (Premiere rounds seconds down) and a PNG
+ > subclip becomes 12 hours. The path is XML.
- Regras do aperto: o silêncio é medido **pelo áudio** (< -45 dB), nunca pela
- distância entre palavras do whisper, porque o whisper estica a palavra por cima
- da pausa (o "or" da 1.1 tinha 4,7 s). Pausa > 0,15 s fica com 0,10 s. Arrasto
- é palavra de até 3 letras com som contínuo > 0,40 s, medido dentro das bordas
- do whisper, porque em fala corrida o som não para entre as palavras. Do arrasto
- saem só o miolo da vogal, ficando 0,15 s de cabeça e 0,08 s de rabo. Selo e
- rótulo só andam com o corte; o b-roll é cortado junto.
- O script aborta quando a duração de uma peça `.mov` não bate com o plano: sinal
- de que o plano do motor mudou depois do render e o mapa palavra→quadro está errado.
+ ## Flow
- ## Aprender com a edição do dono
+ 0. **The user may be working in Premiere right now.** `set_active_sequence` switches their
+ screen, and a clip's node ID changes when the active sequence changes. Before each edit,
+ re-read the ID with `get_clip_at_position`, and warn before a round of changes.
+ 1. **Connection, read-only.** `verify_premiere_connection` (backend `cep`) and
+ `get_project_info`. If a call times out at 120 s, a dialog is open in Premiere: ask the user to
+ close it and fire nothing else until they do.
+ 2. **Check the source** with `ffprobe`: resolution, fps (`r_frame_rate` and `avg_frame_rate`),
+ audio present, rotation. iPhone MOV is VFR and Premiere interprets it at 29.9992.
+ 3. **Generate the XML**: `python scripts/premiere_xml.py <plan.json> --saida <dir>`.
+ 4. **Import into the open project**: `import_media` with the `.xml` (`suppress_ui: true`). Do not
+ use `import_fcp_xml`, which creates ANOTHER project. One lesson per call.
+ 5. **Relink**: XML media comes in offline even with the right path. `search_project_items` with
+ `offline_only`, then `relink_media` of each item to the same path. Up to 4 in parallel works.
+ 6. **Punch-in** (face shots only): `python scripts/premiere_xml.py --motion <plan.json>` gives the
+ table. Apply with `set_effect_property`, effect `Movimento`, `Escala` (%) and normalized
+ `Posição` `[x/w, y/h]`. In PT-BR `set_clip_properties_batch` fails.
+ **Animated push-in** (only with the user's approval, showing first and last frame sheets
+ first): `add_keyframe` only accepts numbers, so Position does not animate. Put the
+ `Ponto de ancoragem` (anchor point) on the face `[cx/w, cy/h]` and `Posição` on the target
+ `[0.5, 0.38]`, and animate only `Escala`. Keyframe time is SOURCE time: use `inPoint` from
+ `get_clip_properties` and `inPoint + duration − 1 frame`.
+ 7. **Marker colors**: the XML carries no color. Use `update_marker(time_seconds, color=1)` (red)
+ on shot markers; speech stays default (0, green).
+ 8. **Read back and check**: `get_full_sequence_info`. When the result goes to a file (large
+ output), run `python scripts/premiere_xml.py --conferir <plan.json> <file>`. When it comes
+ inline, check cut count, total frames, markers, format and A1.
+ 9. **Look at frames**: `capture_frame` at 2 or 3 points (one punch-in of each scale and the most
+ suspicious cut) against the reference render, if any. A Position stuck at the clamp limit means
+ a badly detected face.
+ 10. **Save** (`save_project`) and **tidy**: sequences and media in one bin per delivery. CEP does
+ not delete project items: test leftovers go to a bin `_descartar (teste do agente)` and the
+ sequence name gets `[descartar]`, for the user to delete.
- Quando o dono corrige à mão uma sequência gerada (ex.: refez o fecho), não
- adivinhe o que ele quis. Exporte a versão dele e compare com a sua:
- `python aprende.py <meu.xml> <dono.xml>`. A comparação é por trecho do bruto,
- e mostra o que ele DEVOLVEU, o que ele CORTOU A MAIS e quais palavras estão em
- cada trecho. Pra repetir o fecho dele numa versão nova:
- `aprende.py <novo.xml> <dono.xml> --fecho MM:SS.s <saida.xml> "<nome>"`. Os
- clipes dele entram no fim com volume e corte, e o `masterclipid` é remapeado,
- porque o id dele aponta pra outra mídia e o import trava. Mudança que se repete
- vira número na skill ou linha no roteiro (`fecho.tsv`, `roteiro.tsv`), não
- conserto à mão de novo.
+ ## Tightening an already edited sequence (pauses, drags, fumbles)
- Pra a edição dele sobreviver a toda versão nova: `aprende.py <meu> <dono> --grava`
- escreve `edicao_dono.tsv` (corta/mantém por trecho do bruto) e o `aperta_final`
- repete sozinho. Trecho chato (ditar prompt) vira rampa: linha no `rampas.tsv`
- (`rampa.py`: acelera em degraus até 3x, voz sempre audível, sem zoom — o dono faz
- o zoom à mão — e selo ACELERADO).
+ When the user already edited the sequence by hand, **do not regenerate from the engine**: their
+ edit is lost. The path is an XML round trip into a new sequence alongside:
- Corte de conteúdo (rodeio, frase dita duas vezes, tropeço) é do agente e fica
- separado: `fala.py <modulo> <aula>` imprime a fala por frase com o tempo da fonte;
- `agente.py <modulo> <aula> <fonte> "começo|fim" ... --grava` acha o trecho pelo
- texto e escreve `edicao_agente.tsv`, que o `aperta_final` lê junto do do dono.
- Corte que muda promessa (curso citado, preço, prazo) entra, mas vai pra pergunta.
+ 1. `create_project_backup` of the `.prproj`.
+ 2. For each lesson: `set_active_sequence` (warn first, it switches the user's screen) and
+ `export_as_fcp_xml` to `_apertada/aula-N.xml`.
+ 3. Run the project's tighten script (`aperta_final.py _apertada/aula-N.xml`). It outputs
+ `aula-N-apertada.xml` + `.cortes.tsv`, each cut with time, reason and word.
+ 4. `import_media` of the tightened XML with `target_bin` (the user's bin), media into
+ `<bin>/_mídia`, then step 5 (relink) and 8 (check). The original stays intact for comparison;
+ re-import gets `--sufixo v2` and never overwrites a tightened version the user already edited.
- ## Cards: aviso e logo, feitos no Figma
+ Tightening rules: silence is measured **from the audio** (< -45 dB), never from the gap between
+ whisper words, because whisper stretches a word over the pause (one word spanned 4.7 s). A pause
+ > 0.15 s keeps 0.10 s. A drag is a word of up to 3 letters with continuous sound > 0.40 s,
+ measured inside whisper's edges, because in running speech the sound does not stop between
+ words. From a drag only the vowel's core is removed, keeping 0.15 s of head and 0.08 s of tail.
+ Badge and label move only with the cut; b-roll is cut along. The script aborts when a `.mov`
+ piece's duration does not match the plan: sign the engine's plan changed after the render and
+ the word→frame map is wrong.
- O card nasce no deck da aula (Figma Slides), nunca desenhado no Premiere:
- 1. `python scripts/figma_cards.py <lote.json> <lote.js>` (formato no topo do script; ícones em
- `scripts/icones/`, Simple Icons CC0 com a cor oficial; marca que não está lá entra como texto).
- 2. Rodar o JS com `use_figma` (skills `figma-use`, `figma-use-slides`). Cada aula ganha uma linha
- `Edição N (não apresentar)`; se a linha encher, abre `Edição N b`.
- 3. `get_screenshot` com `contentsOnly` de cada card → PNG em `_build/aulaN/sobre/`.
- 4. No Premiere: `import_media`, `overwrite_clip` na trilha MAIS ALTA livre, `set_clip_duration`,
- `set_effect_property` (Posição normalizada, Escala), fade da casa (`references/armadilhas.md`).
- Achar o segundo da fala: `onde.py <modulo> <aula> <xml|estrutura.json> "frase"`.
- Card que repete o que o slide já mostra não entra.
+ ## Learning from the user's edit
- ## Cobrir fala com b-roll e timelapse
+ When the user fixes a generated sequence by hand (e.g. redid the ending), do not guess what they
+ meant. Export their version and compare with yours: `python aprende.py <mine.xml> <theirs.xml>`.
+ The comparison is per raw stretch and shows what they RESTORED, what they CUT FURTHER and which
+ words are in each stretch. To repeat their ending on a new version:
+ `aprende.py <new.xml> <theirs.xml> --fecho MM:SS.s <out.xml> "<name>"`. Their clips go in at the
+ end with volume and cut, and `masterclipid` is remapped, because their id points to other media
+ and the import hangs. A change that repeats becomes a number in the skill or a line in the
+ script (`fecho.tsv`, `roteiro.tsv`), not another hand fix.
- (`onde.py`, `fonte_iphone.py`, `entrega.py`, `trecho.py` moram no projeto, ao lado do `aulas.py`.)
+ For their edit to survive every new version: `aprende.py <mine> <theirs> --grava` writes
+ `edicao_dono.tsv` (cut/keep per raw stretch) and the tighten script repeats it on its own. A
+ boring stretch (dictating a prompt) becomes a ramp: a line in `rampas.tsv` (`rampa.py`: speeds up
+ in steps up to 3x, voice always audible, no zoom (the user zooms by hand), and an ACELERADO badge).
- Trecho de rosto longo sem tela pede b-roll. Regras:
- - **Cópia só vídeo** (`ffmpeg -an`) de cada b-roll: o áudio da câmera nunca entra na A1.
- - **Cortar o pedaço exato no ffmpeg** (`cortes/<aula>_<tl>_<fonte>.mov`) e colocar inteiro:
- `set_item_in_out` não aplica o in nesta versão.
- - Trilha acima do rosto e abaixo dos cards; nunca por cima de um card ou rótulo do motor.
- - **Olhar cada quadro antes**: barra lateral do Claude, seletor de conta, e-mail, nome de cliente.
- Borrar (`boxblur` na região) ou trocar de b-roll. Na dúvida, troca.
- - Fonte de iPhone: `fonte_iphone.py` (HDR→SDR e rotação) ANTES do `trecho.py`.
+ Content cuts (rambling, sentence said twice, stumble) belong to the agent and stay separate:
+ `fala.py <module> <lesson>` prints speech per sentence with source time;
+ `agente.py <module> <lesson> <source> "start|end" ... --grava` finds the stretch by text and
+ writes `edicao_agente.tsv`, which the tighten script reads together with the user's. A cut that
+ changes a promise (course mentioned, price, deadline) goes in, but goes to a question.
- ## Trocar uma peça renderizada que já está na sequência
+ ## Cards: notices and logos, made in Figma
- Peça refeita (corte de fala, cor) sai com NOME NOVO (`-2.mov`): reimportar o mesmo caminho é ignorado.
- `import_media` → `remove_from_timeline` do vídeo E do áudio (saem separados) → `overwrite_clip` em 0 →
- `get_sequence_structure` pra conferir a duração. Job do Media Encoder já na fila guarda a versão
- antiga: ponha um job novo com outro nome e peça pro dono apagar o velho.
+ The card is born in the lesson deck (Figma Slides), never drawn in Premiere:
+ 1. `python scripts/figma_cards.py <batch.json> <batch.js>` (format at the top of the script;
+ icons in `scripts/icones/`, Simple Icons CC0 in the official color; a brand not there goes in
+ as text, never an invented logo).
+ 2. Run the JS with `use_figma`. Each lesson gets a row `Edição N (não apresentar)`; if the row
+ fills up, it opens `Edição N b`.
+ 3. `get_screenshot` with `contentsOnly` of each card → PNG in `_build/aulaN/sobre/`.
+ 4. In Premiere: `import_media`, `overwrite_clip` on the HIGHEST free track, `set_clip_duration`,
+ `set_effect_property` (normalized Position, Scale), house fade (`references/pitfalls.md`).
+ Find the second of the line with the project's `onde.py <module> <lesson> <xml|json> "phrase"`.
- ## Entrega
+ A card that repeats what the slide already shows does not go in.
- 1. Nome da sequência é o nome do arquivo: `Aula N · Título`, sem `v2`/`apertada`/`nova`.
- O `aperta_final` sai com `· nova`; o dono aprova e o sufixo sai.
- 2. `save_project`, `check_offline_media` = 0, e por aula: `set_active_sequence` →
- `add_to_render_queue` (preset `01 - Match Source - High bitrate`, saída `<N · Título>.mp4`,
- sem `?` no nome) e `start_batch_encode` (o dono não precisa apertar ▶). O MP4 sai com a
- sequência inteira: aparar o preto do fim com `ffmpeg -t ... -c copy`. Vigiar com `sensor_export.py`.
- 3. `python entrega.py <pasta export> <pasta final>`: loudnorm 2 passes a -14 LUFS / -1,5 dBTP com o
- vídeo copiado, depois `.srt`/`.vtt` pelo `legenda_whisper.py`. Roda de novo só no que falta.
+ ## Covering speech with b-roll and timelapse
- ## Relatório
+ A long face stretch with no screen needs b-roll. Rules:
+ - **Video-only copy** (`ffmpeg -an`) of each b-roll: camera audio never goes into A1.
+ - **Cut the exact piece in ffmpeg** (`cortes/<lesson>_<tl>_<source>.mov`) and place it whole:
+ `set_item_in_out` does not apply the in point in this version.
+ - Track above the face and below the cards; never over a card or an engine label.
+ - **Look at every frame first**: Claude's sidebar, account picker, e-mail, client name. Blur
+ (`boxblur` on the region) or swap the b-roll. When in doubt, swap.
+ - iPhone source: convert HDR→SDR and fix rotation BEFORE cutting the piece.
- Uma tabela por sequência: cortes, quadros, duração vs plano, marcadores, formato,
- faixas. Diga o que mudou em relação ao plano (quadro puxado, marcador empurrado,
- posição corrigida à mão) e mande a correção pra quem gera o plano.
+ ## Swapping a rendered piece already in the sequence
- Armadilhas medidas, com causa e conserto: `references/armadilhas.md`. Leia antes
- da primeira importação da sessão.
+ A redone piece (speech cut, color) gets a NEW NAME (`-2.mov`): re-importing the same path is
+ ignored. `import_media` → `remove_from_timeline` of the video AND the audio (they come out
+ separately) → `overwrite_clip` at 0 → `get_sequence_structure` to check the duration. A Media
+ Encoder job already queued keeps the old version: queue a new job with another name and ask the
+ user to delete the old one.
+
+ ## Delivery
+
+ 1. Sequence name is the file name: `Aula N · Título`, no `v2`/`apertada`/`nova`. The tighten
+ script outputs `· nova`; the user approves and the suffix goes.
+ 2. `save_project`, `check_offline_media` = 0, and per lesson: `set_active_sequence` →
+ `add_to_render_queue` (preset `01 - Match Source - High bitrate`, output `<N · Título>.mp4`,
+ no `?` in the name) and `start_batch_encode` (the user does not need to press ▶). If the queue
+ never starts, use `export_sequence` instead (see pitfalls). The MP4 carries the whole
+ sequence: trim the black at the end with `ffmpeg -t ... -c copy`. Watch the output folder
+ (the project's `sensor_export.py`).
+ 3. Loudness: 2-pass loudnorm to -14 LUFS / -1.5 dBTP with the video copied, then `.srt`/`.vtt`
+ from whisper (the project's `entrega.py` + `legenda_whisper.py`). Re-run only on what is
+ missing.
+
+ ## Report
+
+ One table per sequence: cuts, frames, duration vs plan, markers, format, tracks. Say what changed
+ relative to the plan (frame pulled, marker pushed, position fixed by hand) and send the fix to
+ whoever generates the plan.
+
+ Measured pitfalls, with cause and fix: `references/pitfalls.md`. Read before the session's first
+ import. Where the rules came from: `references/cases.md`.