feat: destacado no deploy o resumo que será gravado na mensagem do

- destacado no deploy o resumo que será gravado na mensagem do commit

Resumo:
- 8 arquivos alterados
- 1 novos
- 7 modificados
- 0 removidos

 7 files changed, 143 insertions(+), 11 deletions(-)

Arquivos:
  - .jhonny/analises.db
  - admin/deploy.command
  - code/engine/executar_scanner.py
  - code/engine/integracoes/midia/__init__.py
  - code/engine/integracoes/visual/apple_vision.py
  - code/engine/scanner/configuracao_visual.py
  - code/engine/scanner/visual.py
  - code/engine/integracoes/midia/compactador_de_proxy.py
This commit is contained in:
João Henrique
2026-09-08 18:40:52 -04:00
parent 5cc50f00a4
commit 400c3c93dd
8 changed files with 313 additions and 11 deletions
+42
View File
@@ -15,13 +15,41 @@ CAMINHO_DO_CODIGO = Path(__file__).resolve().parent.parent
if str(CAMINHO_DO_CODIGO) not in sys.path:
sys.path.insert(0, str(CAMINHO_DO_CODIGO))
def _encontrar_caminho_da_midia() -> str:
"""Localiza a pasta com ``ffmpeg``/``ffprobe`` para o PATH dos subprocessos.
O Premiere é aberto pelo Finder/LaunchServices (sem shell de login), então o
PATH herdado pelo painel não costuma trazer ``/opt/homebrew/bin``. Essa
função devolve o primeiro caminho conhecido onde essas ferramentas existem,
ou string vazia quando nenhum é encontrado.
"""
for candidato in ("/opt/homebrew/bin", "/usr/local/bin"):
if Path(candidato, "ffprobe").is_file() or Path(candidato, "ffmpeg").is_file():
return candidato
return ""
# O mesmo ajuste de PATH já usado em ``admin/deploy.command`` e
# ``admin/install.command``: sem ele, a extração de frames e a transcrição
# falham com ``No such file or directory: 'ffprobe'``.
CAMINHO_DA_MIDIA = _encontrar_caminho_da_midia()
if CAMINHO_DA_MIDIA:
os.environ["PATH"] = CAMINHO_DA_MIDIA + os.pathsep + os.environ.get("PATH", "")
from engine.integracoes.premiere.conversores import ConversorDeTimeline
from engine.scanner import ConfiguracaoVisualDoScanner, criar_detector_apple_vision, gerar_relatorio_visual
from engine.scanner.transcricao_da_timeline import TranscricaoDaTimeline
from engine.persistencia import RepositorioDeAnalisesSQLite, RepositorioDeTimelineSQLite
MODELO_DIARIZACAO_PADRAO = "pyannote/speaker-diarization-community-1"
# O padrão do banco de análises é ``.jhonny/analises.db`` relativo ao cwd, mas o
# painel roda o scanner com cwd ``code``. Fixamos o caminho absoluto na raiz do
# projeto para garantir que a gravação sempre caia no mesmo banco.
CAMINHO_DO_BANCO = CAMINHO_DO_CODIGO.parent / ".jhonny" / "analises.db"
def nome_seguro(nome: str) -> str:
return re.sub(r"[^A-Za-z0-9._-]+", "-", nome.strip()).strip(".-") or "timeline"
@@ -41,6 +69,13 @@ def executar(entrada: Path, saida: Path) -> Path:
print(json.dumps({"evento": "progresso", "etapa": etapa, "percentual": round(percentual, 1),
"clipe": clipe}, ensure_ascii=False), flush=True)
# Persistência: timeline/estrutura + análise visual + transcrição vão para o
# banco SQLite do projeto (mesmo dos relatórios gerados).
repositorio_de_timeline = RepositorioDeTimelineSQLite(CAMINHO_DO_BANCO)
repositorio_de_analises = RepositorioDeAnalisesSQLite(CAMINHO_DO_BANCO)
emitir("Persistindo timeline", 0)
repositorio_de_timeline.registrar_timeline(timeline)
resultados = []
for clipe in clipes:
peso = max(1, math.ceil((clipe.intervalo_na_origem or clipe.intervalo_na_timeline).duracao /
@@ -60,6 +95,11 @@ def executar(entrada: Path, saida: Path) -> Path:
arquivo = Path(tempfile.gettempdir()) / f"scanner-{clipe.identificador}.json"
gerar_relatorio_visual(clipe, resultado, arquivo, configuracao.intervalo_em_segundos)
resultados.append(json.loads(arquivo.read_text(encoding="utf-8")))
# Grava no banco os fatos visuais e as cenas detectadas deste clipe.
repositorio_de_analises.registrar_evidencias_visuais(
timeline.identificador, clipe.identificador, resultado.evidencias)
repositorio_de_analises.registrar_cenas(
timeline.identificador, resultado.cenas, clipe.identificador)
except Exception as erro:
resultados.append({"clipe": {"identificador": clipe.identificador, "nome": clipe.nome,
"arquivo_original": clipe.arquivo}, "erro": str(erro)})
@@ -111,6 +151,8 @@ def executar(entrada: Path, saida: Path) -> Path:
transcricoes = TranscricaoDaTimeline(provider, diretoria_de_trabalho=pasta / ".cache-audio",
diarizador=diarizador).executar(
Timeline(timeline.identificador, timeline.nome, faixas=faixas_de_audio))
# Persiste a transcrição (segmentos + falas) no banco de análises.
repositorio_de_analises.registrar_transcricoes(timeline.identificador, transcricoes)
for transcricao in transcricoes:
transcricoes_por_clipe[transcricao.identificador_do_clipe] = [
{"inicio": item.inicio, "fim": item.fim, "texto": item.texto,