feat: criado repositório jhonny-editor no Gitea
criado repositório jhonny-editor no Gitea adicionado script admin/deploy.command com commit automático atualizado admin/DEV-NOTES.md com template limpo Resumo: - 4 arquivos alterados - 0 novos - 4 modificados - 0 removidos 4 files changed, 101 insertions(+), 4 deletions(-) Arquivos: - code/engine/editor/aplicador_de_plano_de_edicao.py - code/engine/editor/escrita/escrita_no_editor.py - code/engine/testes/duplos_de_premiere.py - code/engine/testes/test_aplicador_de_plano_de_edicao.py
This commit is contained in:
@@ -215,8 +215,19 @@ class AplicadorDePlanoDeEdicao:
|
|||||||
)
|
)
|
||||||
|
|
||||||
pontos_de_corte = self._calcular_pontos_de_corte(faixas_afetadas, acao, arquivo_de_origem)
|
pontos_de_corte = self._calcular_pontos_de_corte(faixas_afetadas, acao, arquivo_de_origem)
|
||||||
|
razor_bem_sucedido = True
|
||||||
for ponto in pontos_de_corte:
|
for ponto in pontos_de_corte:
|
||||||
self.escrita.dividir_todas_as_faixas(ponto)
|
try:
|
||||||
|
self.escrita.dividir_todas_as_faixas(ponto)
|
||||||
|
except ErroDeEdicao:
|
||||||
|
razor_bem_sucedido = False
|
||||||
|
break
|
||||||
|
|
||||||
|
if not razor_bem_sucedido:
|
||||||
|
raise ErroDeMapeamentoDeTempo(
|
||||||
|
f"O razor falhou ao cortar as faixas em {acao.inicio:.3f}-{acao.fim:.3f}s "
|
||||||
|
f"(Premiere 26.x QE no-op); nada foi removido."
|
||||||
|
)
|
||||||
|
|
||||||
faixas = self._timeline_atual().faixas
|
faixas = self._timeline_atual().faixas
|
||||||
for faixa_id in {f.identificador for f in faixas_afetadas}:
|
for faixa_id in {f.identificador for f in faixas_afetadas}:
|
||||||
|
|||||||
@@ -85,9 +85,16 @@ class EscritaNoEditor:
|
|||||||
Usa ``razor_all_tracks`` para garantir que vídeo e áudio sejam
|
Usa ``razor_all_tracks`` para garantir que vídeo e áudio sejam
|
||||||
divididos no mesmo ponto de tempo, evitando drift entre chamadas
|
divididos no mesmo ponto de tempo, evitando drift entre chamadas
|
||||||
separadas de ``split_clip``.
|
separadas de ``split_clip``.
|
||||||
|
|
||||||
|
Levanta :class:`ErroDeEscritaNoEditor` se o razor reportar uma
|
||||||
|
falha parcial (algumas faixas cortadas, outras não) — o Premiere
|
||||||
|
26.x tem um bug QE que faz o razor falhar silenciosamente em
|
||||||
|
algumas instalações. Uma falha parcial deixa a timeline num estado
|
||||||
|
inconsistente que corrompe operações subsequentes (remoção, zoom,
|
||||||
|
marcador), então a operação é recusada inteiramente.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
self.cliente_mcp.chamar(
|
resultado = self.cliente_mcp.chamar(
|
||||||
"razor_all_tracks",
|
"razor_all_tracks",
|
||||||
{"time_seconds": instante_da_timeline, "track_type": "both"},
|
{"time_seconds": instante_da_timeline, "track_type": "both"},
|
||||||
)
|
)
|
||||||
@@ -95,6 +102,13 @@ class EscritaNoEditor:
|
|||||||
raise ErroDeEscritaNoEditor(
|
raise ErroDeEscritaNoEditor(
|
||||||
f"Não foi possível cortar todas as faixas em {instante_da_timeline:.3f}s: {erro}"
|
f"Não foi possível cortar todas as faixas em {instante_da_timeline:.3f}s: {erro}"
|
||||||
) from erro
|
) from erro
|
||||||
|
elegiveis = int(resultado.get("eligibleTracks", 0))
|
||||||
|
cortados = int(resultado.get("razored", 0))
|
||||||
|
if elegiveis > 0 and cortados < elegiveis:
|
||||||
|
raise ErroDeEscritaNoEditor(
|
||||||
|
f"O razor cortou apenas {cortados} de {elegiveis} faixa(s) elegíveis "
|
||||||
|
f"em {instante_da_timeline:.3f}s (Premiere 26.x QE no-op)"
|
||||||
|
)
|
||||||
|
|
||||||
def remover_trecho(self, identificador_do_clipe: str) -> None:
|
def remover_trecho(self, identificador_do_clipe: str) -> None:
|
||||||
"""Remove o clipe da timeline fechando o espaço que ele ocupava.
|
"""Remove o clipe da timeline fechando o espaço que ele ocupava.
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ class ClienteMCPFalso(ClienteMCP):
|
|||||||
sem_origem_no_audio: bool = False,
|
sem_origem_no_audio: bool = False,
|
||||||
clipe_unico: bool = False,
|
clipe_unico: bool = False,
|
||||||
desvio_do_split: float = 0.0,
|
desvio_do_split: float = 0.0,
|
||||||
|
simular_falha_razor: bool = False,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""``sem_origem_no_audio`` reproduz o bug real do bridge: relatar clipes de
|
"""``sem_origem_no_audio`` reproduz o bug real do bridge: relatar clipes de
|
||||||
áudio sem ``inPoint``/``outPoint`` na resposta de ``get_active_sequence``.
|
áudio sem ``inPoint``/``outPoint`` na resposta de ``get_active_sequence``.
|
||||||
@@ -59,9 +60,14 @@ class ClienteMCPFalso(ClienteMCP):
|
|||||||
|
|
||||||
``desvio_do_split`` reproduz o drift real do ``split_clip``: em vez
|
``desvio_do_split`` reproduz o drift real do ``split_clip``: em vez
|
||||||
de cortar exatamente no instante pedido, corta em
|
de cortar exatamente no instante pedido, corta em
|
||||||
``instante + desvio_do_split``."""
|
``instante + desvio_do_split``.
|
||||||
|
|
||||||
|
``simular_falha_razor`` reproduz o bug QE do Premiere 26.x: o
|
||||||
|
``razor_all_tracks`` reporta clipes elegíveis mas não corta nenhum
|
||||||
|
(``razored=0, eligibleTracks=N``)."""
|
||||||
self.sem_origem_no_audio = sem_origem_no_audio
|
self.sem_origem_no_audio = sem_origem_no_audio
|
||||||
self.desvio_do_split = desvio_do_split
|
self.desvio_do_split = desvio_do_split
|
||||||
|
self.simular_falha_razor = simular_falha_razor
|
||||||
self.chamadas: list[tuple[str, dict[str, Any]]] = []
|
self.chamadas: list[tuple[str, dict[str, Any]]] = []
|
||||||
self._proximo_id = 0
|
self._proximo_id = 0
|
||||||
self._historico: list[dict[str, list[_ClipeFalso]]] = []
|
self._historico: list[dict[str, list[_ClipeFalso]]] = []
|
||||||
@@ -230,9 +236,27 @@ class ClienteMCPFalso(ClienteMCP):
|
|||||||
Reproduz o ``razor_all_tracks`` do bridge real: corta todas as faixas
|
Reproduz o ``razor_all_tracks`` do bridge real: corta todas as faixas
|
||||||
de vídeo e/ou áudio no mesmo ponto de tempo, garantindo
|
de vídeo e/ou áudio no mesmo ponto de tempo, garantindo
|
||||||
sincronização entre elas.
|
sincronização entre elas.
|
||||||
|
|
||||||
|
Quando ``simular_falha_razor`` está ativo, reporta clipes elegíveis
|
||||||
|
mas não corta nenhum, reproduzindo o bug QE do Premiere 26.x.
|
||||||
"""
|
"""
|
||||||
instante_pedido = float(argumentos["time_seconds"])
|
instante_pedido = float(argumentos["time_seconds"])
|
||||||
track_type = argumentos.get("track_type", "both")
|
track_type = argumentos.get("track_type", "both")
|
||||||
|
|
||||||
|
elegiveis = 0
|
||||||
|
for identificador_da_faixa, faixa in self._faixas.items():
|
||||||
|
if track_type == "video" and not identificador_da_faixa.startswith("video_"):
|
||||||
|
continue
|
||||||
|
if track_type == "audio" and not identificador_da_faixa.startswith("audio_"):
|
||||||
|
continue
|
||||||
|
for clipe in faixa:
|
||||||
|
if clipe.inicio_na_timeline < instante_pedido < clipe.fim_na_timeline:
|
||||||
|
elegiveis += 1
|
||||||
|
break
|
||||||
|
|
||||||
|
if self.simular_falha_razor:
|
||||||
|
return {"razored": 0, "eligibleTracks": elegiveis, "verified": False}
|
||||||
|
|
||||||
cortados = 0
|
cortados = 0
|
||||||
self._salvar_estado()
|
self._salvar_estado()
|
||||||
for identificador_da_faixa, faixa in self._faixas.items():
|
for identificador_da_faixa, faixa in self._faixas.items():
|
||||||
@@ -262,7 +286,7 @@ class ClienteMCPFalso(ClienteMCP):
|
|||||||
faixa[posicao:posicao + 1] = [parte_esquerda, parte_direita]
|
faixa[posicao:posicao + 1] = [parte_esquerda, parte_direita]
|
||||||
cortados += 1
|
cortados += 1
|
||||||
break
|
break
|
||||||
return {"razored": cortados, "verified": True}
|
return {"razored": cortados, "eligibleTracks": elegiveis, "verified": True}
|
||||||
|
|
||||||
def _simular_ripple_delete(self, argumentos: dict[str, Any]) -> dict[str, Any]:
|
def _simular_ripple_delete(self, argumentos: dict[str, Any]) -> dict[str, Any]:
|
||||||
"""Remove o clipe pedido e fecha o espaço deixado na mesma faixa."""
|
"""Remove o clipe pedido e fecha o espaço deixado na mesma faixa."""
|
||||||
|
|||||||
@@ -317,6 +317,54 @@ class TesteAplicadorDePlanoDeEdicao(unittest.TestCase):
|
|||||||
self.assertLess(nomes.index("remove_linked_from_timeline"), nomes.index("duplicate_linked_range_to_start"))
|
self.assertLess(nomes.index("remove_linked_from_timeline"), nomes.index("duplicate_linked_range_to_start"))
|
||||||
self.assertLess(nomes.index("duplicate_linked_range_to_start"), nomes.index("set_clip_properties"))
|
self.assertLess(nomes.index("duplicate_linked_range_to_start"), nomes.index("set_clip_properties"))
|
||||||
|
|
||||||
|
def test_corte_falha_limpo_quando_razor_nao_corta_nenhuma_faixa(self):
|
||||||
|
"""Quando o razor_all_tracks falha (QE no-op do Premiere 26.x),
|
||||||
|
o corte deve falhar sem corromper a timeline para operações
|
||||||
|
subsequentes — nenhuma remoção é tentada e marcadores/zooms
|
||||||
|
continuam funcionando."""
|
||||||
|
cliente = ClienteMCPFalso(simular_falha_razor=True)
|
||||||
|
aplicador = AplicadorDePlanoDeEdicao(
|
||||||
|
acesso_ao_editor=AcessoAoEditor(AcessoATimeline(cliente)),
|
||||||
|
conversor_de_timeline=ConversorDeTimeline(),
|
||||||
|
escrita=EscritaNoEditor(cliente),
|
||||||
|
mapeador=MapeadorDeTempoDeOrigemParaTimeline(),
|
||||||
|
)
|
||||||
|
plano = PlanoDeEdicao("0E6A8290.mp4", (
|
||||||
|
AcaoDeEdicao(TipoDeAcao.CORTE, inicio=0.0, fim=40.6, motivo="bastidor"),
|
||||||
|
AcaoDeEdicao(TipoDeAcao.MARCADOR, inicio=90.0, fim=90.5, motivo="ok", parametros={"content": "X"}),
|
||||||
|
))
|
||||||
|
resultado = aplicador.aplicar(plano)
|
||||||
|
self.assertFalse(resultado.todas_bem_sucedidas)
|
||||||
|
self.assertEqual(len(resultado.falhas), 1)
|
||||||
|
self.assertIn("razor", resultado.falhas[0].detalhe.lower())
|
||||||
|
self.assertTrue(any(c[0] == "add_marker" for c in cliente.chamadas))
|
||||||
|
self.assertEqual(cliente.chamadas_de_remocao, [])
|
||||||
|
|
||||||
|
def test_gancho_falha_limpo_quando_razor_nao_corta_nenhuma_faixa(self):
|
||||||
|
"""Quando o razor_all_tracks falha ao isolar o trecho do gancho,
|
||||||
|
a operação deve falhar sem duplicar nada na timeline."""
|
||||||
|
cliente = ClienteMCPFalso(clipe_unico=True, simular_falha_razor=True)
|
||||||
|
aplicador = AplicadorDePlanoDeEdicao(
|
||||||
|
acesso_ao_editor=AcessoAoEditor(AcessoATimeline(cliente)),
|
||||||
|
conversor_de_timeline=ConversorDeTimeline(),
|
||||||
|
escrita=EscritaNoEditor(cliente),
|
||||||
|
mapeador=MapeadorDeTempoDeOrigemParaTimeline(),
|
||||||
|
)
|
||||||
|
plano = PlanoDeEdicao("0E6A8290.mp4", (
|
||||||
|
AcaoDeEdicao(
|
||||||
|
TipoDeAcao.GANCHO,
|
||||||
|
inicio=80.0,
|
||||||
|
fim=90.0,
|
||||||
|
motivo="gancho de abertura",
|
||||||
|
parametros={"target": 0, "keep_original": True},
|
||||||
|
),
|
||||||
|
))
|
||||||
|
resultado = aplicador.aplicar(plano)
|
||||||
|
self.assertFalse(resultado.todas_bem_sucedidas)
|
||||||
|
self.assertEqual(len(resultado.falhas), 1)
|
||||||
|
duplicacoes = [c for c in cliente.chamadas if c[0] == "duplicate_linked_range_to_start"]
|
||||||
|
self.assertEqual(len(duplicacoes), 0)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|||||||
Reference in New Issue
Block a user