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
@@ -0,0 +1,101 @@
"""Testes do decorator que espaça as chamadas ao bridge do Premiere."""
import unittest
from typing import Any
from engine.integracoes.premiere.cliente_mcp import ClienteMCP
from engine.integracoes.premiere.cliente_mcp_com_pausa import ClienteMCPComPausa
from engine.integracoes.premiere.erros_mcp import ErroDeFerramentaMCP
class _ClienteQueRegistra(ClienteMCP):
"""Dublê que só registra as chamadas recebidas e devolve um resultado fixo."""
def __init__(self, erro_a_levantar: Exception | None = None) -> None:
self.chamadas: list[tuple[str, dict[str, Any]]] = []
self.erro_a_levantar = erro_a_levantar
self.conectado = False
def conectar(self) -> None:
"""Marca o dublê como conectado."""
self.conectado = True
def desconectar(self) -> None:
"""Marca o dublê como desconectado."""
self.conectado = False
def esta_conectado(self) -> bool:
"""Estado de conexão registrado pelo dublê."""
return self.conectado
def chamar(self, nome_da_ferramenta: str, argumentos: dict[str, Any] | None = None) -> dict[str, Any]:
"""Registra a chamada e devolve um resultado fixo, ou levanta o erro configurado."""
self.chamadas.append((nome_da_ferramenta, argumentos or {}))
if self.erro_a_levantar is not None:
raise self.erro_a_levantar
return {"ok": True}
class _EsperaFalsa:
"""Substitui ``time.sleep`` registrando as pausas pedidas, sem esperar de verdade."""
def __init__(self) -> None:
self.pausas: list[float] = []
def __call__(self, segundos: float) -> None:
"""Registra a pausa pedida."""
self.pausas.append(segundos)
class TesteClienteMCPComPausa(unittest.TestCase):
"""Garante o espaçamento entre chamadas e a transparência do decorator."""
def setUp(self):
self.interno = _ClienteQueRegistra()
self.espera = _EsperaFalsa()
self.cliente = ClienteMCPComPausa(self.interno, pausa_em_segundos=0.12, esperar=self.espera)
def test_repassa_a_chamada_ao_cliente_interno(self):
resultado = self.cliente.chamar("split_clip", {"time_seconds": 10.0})
self.assertEqual(resultado, {"ok": True})
self.assertEqual(self.interno.chamadas, [("split_clip", {"time_seconds": 10.0})])
def test_nao_pausa_antes_da_primeira_chamada(self):
# A primeira chamada não precisa esperar: não há chamada anterior
# ocupando o processo principal do Premiere.
self.cliente.chamar("get_active_sequence")
self.assertEqual(self.espera.pausas, [])
def test_pausa_entre_chamadas_consecutivas(self):
# Sem essa folga, uma rajada de round-trips síncronos de QE/ExtendScript
# trava a UI do Premiere — foi o que se viu com um plano de 17 cortes.
for _ in range(3):
self.cliente.chamar("split_clip", {"time_seconds": 1.0})
self.assertEqual(self.espera.pausas, [0.12, 0.12])
def test_pausa_tambem_depois_de_uma_chamada_que_falhou(self):
# Uma chamada que falhou ocupou o bridge do mesmo jeito; emendar a
# próxima sem folga é justamente o que derruba a UI.
interno = _ClienteQueRegistra(erro_a_levantar=ErroDeFerramentaMCP("recusado"))
espera = _EsperaFalsa()
cliente = ClienteMCPComPausa(interno, pausa_em_segundos=0.12, esperar=espera)
for _ in range(2):
with self.assertRaises(ErroDeFerramentaMCP):
cliente.chamar("split_clip", {"time_seconds": 1.0})
self.assertEqual(espera.pausas, [0.12])
def test_conexao_e_delegada_sem_pausa(self):
# Conectar/desconectar não são round-trips de edição; não precisam de folga.
self.cliente.conectar()
self.assertTrue(self.cliente.esta_conectado())
self.cliente.desconectar()
self.assertFalse(self.cliente.esta_conectado())
self.assertEqual(self.espera.pausas, [])
def test_pausa_negativa_e_recusada(self):
with self.assertRaises(ValueError):
ClienteMCPComPausa(self.interno, pausa_em_segundos=-0.1)
if __name__ == "__main__":
unittest.main()