feat: criada a aba Análises no painel CEP para ler do banco (anali
- criada a aba Análises no painel CEP para ler do banco (analises.db) e copiar/abrir o J-SOM de som (transcrição por cena) e o de imagem (evidências visuais), com novo módulo engine/gerar_relatorio_de_analises.py e testes - criado o módulo engine/editor (leitura do plano de edição em JSON, tradução de tempos de origem para a timeline ativa e aplicação de cortes/zooms/marcadores no Premiere via MCP) e o ponto de entrada engine/aplicar_plano_de_edicao.py, com testes; corrigido engine/integracoes/premiere/cliente_mcp.py para levantar os erros específicos já declarados em erros_mcp.py em vez de RuntimeError genérico Resumo: - 15 arquivos alterados - 8 novos - 5 modificados - 2 removidos 7 files changed, 172 insertions(+), 8 deletions(-) Arquivos: - .jhonny/analises.db - .jhonny/analises.db-shm - .jhonny/analises.db-wal - code/cep-plugin/index.html - code/cep-plugin/main.js - code/cep-plugin/styles.css - code/engine/integracoes/premiere/cliente_mcp.py - code/engine/aplicar_plano_de_edicao.py - code/engine/editor/ - code/engine/gerar_relatorio_de_analises.py - code/engine/testes/duplos_de_premiere.py - code/engine/testes/test_aplicador_de_plano_de_edicao.py - code/engine/testes/test_gerar_relatorio_de_analises.py - code/engine/testes/test_leitor_de_plano_de_edicao.py - code/engine/testes/test_mapeador_de_tempo.py
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
"""Tradução de tempos de origem para posições na timeline ativa."""
|
||||
|
||||
from .mapeador_de_tempo import MapeadorDeTempoDeOrigemParaTimeline
|
||||
|
||||
__all__ = ["MapeadorDeTempoDeOrigemParaTimeline"]
|
||||
@@ -0,0 +1,81 @@
|
||||
"""Tradução de tempos do arquivo de origem para posições na timeline ativa.
|
||||
|
||||
O plano de edição descreve tudo em segundos do arquivo de origem (regra do
|
||||
formato: nunca compensar para "depois do corte"). Só a sequência do Premiere,
|
||||
lida no momento da aplicação, sabe onde cada trecho de origem está
|
||||
atualmente na timeline — por isso este mapeador nunca usa tempos gravados
|
||||
durante a análise (ex.: ``inicio_na_timeline`` do banco de análises), sempre
|
||||
o clipe lido ao vivo via :class:`~engine.dominio.Clipe`.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from ...dominio import Clipe, Faixa, Timeline
|
||||
from ..erros import ErroDeMapeamentoDeTempo
|
||||
|
||||
_TOLERANCIA_EM_SEGUNDOS = 0.05
|
||||
|
||||
|
||||
class MapeadorDeTempoDeOrigemParaTimeline:
|
||||
"""Localiza clipes pelo tempo de origem e converte instantes para a timeline."""
|
||||
|
||||
def clipes_do_arquivo(self, faixa: Faixa, arquivo_de_origem: str) -> list[Clipe]:
|
||||
"""Clipes da faixa que pertencem a ``arquivo_de_origem``, ordenados pelo início na origem.
|
||||
|
||||
A comparação ignora maiúsculas/minúsculas porque o Premiere e o plano
|
||||
de edição podem registrar a extensão do arquivo em casos diferentes
|
||||
(``.MP4`` vs. ``.mp4``).
|
||||
"""
|
||||
alvo = arquivo_de_origem.strip().lower()
|
||||
candidatos = [
|
||||
clipe for clipe in faixa.clipes
|
||||
if clipe.intervalo_na_origem is not None and (clipe.nome or "").strip().lower() == alvo
|
||||
]
|
||||
return sorted(candidatos, key=lambda clipe: clipe.intervalo_na_origem.inicio)
|
||||
|
||||
def localizar_clipe_no_instante(self, clipes: list[Clipe], instante_de_origem: float) -> Clipe | None:
|
||||
"""Devolve o clipe cujo intervalo de origem contém ``instante_de_origem``, ou ``None``.
|
||||
|
||||
Tolera pequenas diferenças de arredondamento (meio quadro) nas bordas
|
||||
do intervalo.
|
||||
"""
|
||||
for clipe in clipes:
|
||||
intervalo = clipe.intervalo_na_origem
|
||||
if intervalo is None:
|
||||
continue
|
||||
if intervalo.inicio - _TOLERANCIA_EM_SEGUNDOS <= instante_de_origem <= intervalo.fim + _TOLERANCIA_EM_SEGUNDOS:
|
||||
return clipe
|
||||
return None
|
||||
|
||||
def instante_na_timeline(self, clipe: Clipe, instante_de_origem: float) -> float:
|
||||
"""Converte um instante de origem, dentro do intervalo de origem de ``clipe``, para a timeline.
|
||||
|
||||
Levanta :class:`ErroDeMapeamentoDeTempo` se ``clipe`` não tiver
|
||||
intervalo de origem conhecido — não há como calcular o deslocamento
|
||||
sem ele.
|
||||
"""
|
||||
if clipe.intervalo_na_origem is None:
|
||||
raise ErroDeMapeamentoDeTempo(
|
||||
f"O clipe {clipe.identificador!r} não tem intervalo de origem conhecido; "
|
||||
"não é possível traduzir o tempo para a timeline."
|
||||
)
|
||||
deslocamento = instante_de_origem - clipe.intervalo_na_origem.inicio
|
||||
return clipe.intervalo_na_timeline.inicio + deslocamento
|
||||
|
||||
def instante_na_timeline_por_arquivo(
|
||||
self, timeline: Timeline, faixa: Faixa, arquivo_de_origem: str, instante_de_origem: float
|
||||
) -> float:
|
||||
"""Atalho que localiza o clipe do instante em ``faixa`` e já devolve a posição na timeline.
|
||||
|
||||
Levanta :class:`ErroDeMapeamentoDeTempo` se nenhum clipe de
|
||||
``arquivo_de_origem`` cobrir ``instante_de_origem`` nessa faixa.
|
||||
"""
|
||||
del timeline # mantido na assinatura para leitura clara no local de chamada
|
||||
clipes = self.clipes_do_arquivo(faixa, arquivo_de_origem)
|
||||
clipe = self.localizar_clipe_no_instante(clipes, instante_de_origem)
|
||||
if clipe is None:
|
||||
raise ErroDeMapeamentoDeTempo(
|
||||
f"Nenhum clipe de {arquivo_de_origem!r} na faixa {faixa.identificador!r} "
|
||||
f"cobre o instante {instante_de_origem}s de origem."
|
||||
)
|
||||
return self.instante_na_timeline(clipe, instante_de_origem)
|
||||
Reference in New Issue
Block a user