feat: Alinhado o aplicador Python de plano de edição (aplicador_de

- Alinhado o aplicador Python de plano de edição (aplicador_de_plano_de_edicao.py) com o script apply-editorial-actions.mjs de referência: no-op de split restrito aos padrões reais do bridge, pausa entre chamadas ao MCP, tolerância de borda de corte usa a posição real em vez de rejeitar desvio; adicionado backup automático da sequência (duplicate_sequence) e documentado o comando python code/engine/aplicar_plano_de_edicao.py como via alternativa ao painel CEP.

Resumo:
- 18 arquivos alterados
- 5 novos
- 13 modificados
- 0 removidos

 13 files changed, 704 insertions(+), 133 deletions(-)

Arquivos:
  - .jhonny/analises.db
  - CLAUDE.md
  - code/engine/aplicar_plano_de_edicao.py
  - code/engine/editor/__init__.py
  - code/engine/editor/aplicador_de_plano_de_edicao.py
  - code/engine/editor/escrita/escrita_no_editor.py
  - code/engine/integracoes/premiere/__init__.py
  - code/engine/integracoes/premiere/cliente_mcp.py
  - code/engine/testes/duplos_de_premiere.py
  - code/engine/testes/test_aplicador_de_plano_de_edicao.py
  - code/scripts/apply-editorial-actions.mjs
  - code/src/tools/discovery.ts
  - code/src/tools/timeline.ts
  - code/engine/editor/backup_de_sequencia.py
  - code/engine/integracoes/premiere/cliente_mcp_com_pausa.py
  - code/engine/testes/test_backup_de_sequencia.py
  - code/engine/testes/test_cliente_mcp_com_pausa.py
  - code/engine/testes/test_escrita_no_editor.py
This commit is contained in:
João Henrique
2026-09-09 09:31:29 -04:00
parent 4308c72441
commit 69e931eb45
18 changed files with 1040 additions and 133 deletions
@@ -9,10 +9,33 @@ nomes de ferramenta ou ticks do Premiere.
from __future__ import annotations
import re
from ...integracoes.premiere.cliente_mcp import ClienteMCP
from ...integracoes.premiere.erros_mcp import ErroDeFerramentaMCP
from ..erros import ErroDeEscritaNoEditor
# O bridge recusa a divisão de duas formas diferentes quando o ponto pedido
# JÁ é uma borda de clipe. Ambas são no-op, não falha:
# 1. nenhum clipe atravessa estritamente o instante ("strictly spans");
# 2. o razor roda mas a contagem de clipes da faixa não muda, porque o ponto
# coincide por arredondamento de quadro com uma borda existente.
# Os mesmos dois padrões são reconhecidos por scripts/apply-editorial-actions.mjs,
# que aplica planos por este mesmo bridge.
_RECUSA_POR_NENHUM_CLIPE_ATRAVESSANDO = re.compile(r"strictly spans", re.IGNORECASE)
_RECUSA_COM_CONTAGEM_DE_CLIPES = re.compile(
r"changed the track clip count from (\d+) to (\d+)", re.IGNORECASE
)
def _e_divisao_ja_existente(mensagem_de_erro: str) -> bool:
"""``True`` se a recusa do bridge significa "esse ponto já era uma borda"."""
if _RECUSA_POR_NENHUM_CLIPE_ATRAVESSANDO.search(mensagem_de_erro):
return True
contagem = _RECUSA_COM_CONTAGEM_DE_CLIPES.search(mensagem_de_erro)
return contagem is not None and contagem.group(1) == contagem.group(2)
_CORES_POR_NOME = {
"revisar": 3, # laranja: chama atenção sem parecer um erro (vermelho)
"informar": 5, # branco: marcador neutro
@@ -29,10 +52,17 @@ class EscritaNoEditor:
"""Corta, na timeline, o clipe que cobrir ``instante_da_timeline``.
Devolve ``True`` se um corte novo foi criado e ``False`` quando o
instante já era uma borda de clipe (o Premiere recusa dividir onde
não há clipe atravessando o ponto) — esse segundo caso não é um erro,
instante já era uma borda de clipe — esse segundo caso não é um erro,
é o resultado esperado quando o corte pedido coincide com um corte já
existente da análise de cenas.
existente da análise de cenas, ou quando um plano parcialmente
aplicado é reexecutado.
Só as recusas reconhecidas por :func:`_e_divisao_ja_existente` contam
como no-op. Qualquer outra recusa vira
:class:`ErroDeEscritaNoEditor`: antes, um ``except`` largo tratava
toda :class:`ErroDeFerramentaMCP` como "já era borda", então uma
faixa bloqueada ou uma queda de bridge passava por corte
bem-sucedido e o trecho seguia inteiro na timeline, sem aviso.
"""
try:
self.cliente_mcp.chamar(
@@ -40,17 +70,29 @@ class EscritaNoEditor:
{"time_seconds": instante_da_timeline, "track_index": indice_da_faixa, "track_type": tipo_da_faixa},
)
return True
except ErroDeFerramentaMCP:
return False
except ErroDeFerramentaMCP as erro:
if _e_divisao_ja_existente(str(erro)):
return False
raise ErroDeEscritaNoEditor(
f"Não foi possível dividir a faixa {tipo_da_faixa}_{indice_da_faixa} "
f"em {instante_da_timeline:.3f}s: {erro}"
) from erro
def remover_trecho(self, identificador_do_clipe: str) -> None:
"""Remove o clipe da timeline, fechando o espaço deixado (ripple).
"""Remove o clipe da timeline fechando o espaço que ele ocupava.
Usa ``ripple_delete`` (QE ``rippleDelete``), não
``remove_from_timeline``: numa sequência real com outras faixas
ocupadas, o ``remove_from_timeline`` com ``ripple`` apagou o clipe
mas deixou o buraco aberto, e um buraco no meio do corte é um corte
errado. O ``ripple_delete`` confere, do lado do Premiere, que o
clipe realmente sumiu.
Levanta :class:`ErroDeEscritaNoEditor` se o Premiere recusar a
remoção.
"""
try:
self.cliente_mcp.chamar("remove_from_timeline", {"node_id": identificador_do_clipe, "ripple": True})
self.cliente_mcp.chamar("ripple_delete", {"node_id": identificador_do_clipe})
except ErroDeFerramentaMCP as erro:
raise ErroDeEscritaNoEditor(f"Não foi possível remover o clipe {identificador_do_clipe!r}: {erro}") from erro