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: - 48 arquivos alterados - 26 novos - 19 modificados - 3 removidos 22 files changed, 658 insertions(+), 568 deletions(-) Arquivos: - AGENTS.md - admin/DEV-NOTES.md - admin/OpenCut.command - admin/commit.command - admin/deploy.command - admin/update.command - code/cep-plugin/index.html - code/cep-plugin/main.js - code/cep-plugin/styles.css - code/engine/ARQUITETURA.md - code/engine/arquitetura/README.md - code/engine/gerar_relatorio_timeline.py - code/engine/integracoes/apple_speech/apple_speech_transcriber.swift - code/engine/integracoes/apple_speech/provider_de_transcricao_apple.py - code/engine/integracoes/midia/__init__.py - code/engine/integracoes/whisper/provider_de_transcricao_local.py - code/engine/scanner/__init__.py - code/engine/scanner/coordenacao/__init__.py - code/engine/scanner/descoberta/__init__.py - code/engine/scanner/modelos.py - code/engine/scanner/transcricao_da_timeline.py - code/src/tools/discovery.ts - :memory:.ses - admin/Jhonny.command - admin/inativos/OpenCut.command - admin/inativos/update.command - code/docs/glossario-analise-emocional.md - code/docs/levantamento-apple-vision-e-apple-intelligence.md - code/docs/levantamento-ferramentas-analise-visual-local.md - code/engine/arquitetura/biblioteca-inteligente-de-videos.md - code/engine/executar_scanner.py - code/engine/integracoes/huggingface/ - code/engine/integracoes/midia/extracao_de_metadados.py - code/engine/integracoes/visual/ - code/engine/requirements-visual.txt - code/engine/scanner/configuracao_visual.py - code/engine/scanner/descoberta/descoberta_de_arquivos.py - code/engine/scanner/metadados.py - code/engine/scanner/relatorio_visual.py - code/engine/scanner/retakes/ - code/engine/scanner/visual.py - code/engine/testes/test_analise_visual_local.py - code/engine/testes/test_arquivos_e_metadados.py - code/engine/testes/test_provider_de_transcricao_apple.py - code/relatorios/analise-brools/ - code/relatorios/analise-visual/ - code/relatorios/audio/arquivos/ - code/relatorios/transcricao-timeline.md
This commit is contained in:
@@ -0,0 +1,136 @@
|
||||
"""Entrada local do painel CEP para uma execução configurada do Scanner."""
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import math
|
||||
import os
|
||||
from pathlib import Path
|
||||
import tempfile
|
||||
import re
|
||||
|
||||
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
|
||||
|
||||
|
||||
MODELO_DIARIZACAO_PADRAO = "pyannote/speaker-diarization-community-1"
|
||||
|
||||
|
||||
def nome_seguro(nome: str) -> str:
|
||||
return re.sub(r"[^A-Za-z0-9._-]+", "-", nome.strip()).strip(".-") or "timeline"
|
||||
|
||||
|
||||
def executar(entrada: Path, saida: Path) -> Path:
|
||||
pedido = json.loads(entrada.read_text(encoding="utf-8"))
|
||||
configuracao = ConfiguracaoVisualDoScanner.de_dict(pedido["perfil"])
|
||||
timeline = ConversorDeTimeline().converter(pedido["timeline"])
|
||||
clipes = [clipe for faixa in timeline.faixas if faixa.tipo == "video"
|
||||
and faixa.indice in configuracao.faixas_de_video for clipe in faixa.clipes]
|
||||
total_estimado = sum(max(1, math.ceil((clipe.intervalo_na_origem or clipe.intervalo_na_timeline).duracao /
|
||||
configuracao.intervalo_em_segundos)) + 1 for clipe in clipes) or 1
|
||||
concluidos = 0
|
||||
|
||||
def emitir(etapa: str, percentual: float, clipe: str = "") -> None:
|
||||
print(json.dumps({"evento": "progresso", "etapa": etapa, "percentual": round(percentual, 1),
|
||||
"clipe": clipe}, ensure_ascii=False), flush=True)
|
||||
|
||||
resultados = []
|
||||
for clipe in clipes:
|
||||
peso = max(1, math.ceil((clipe.intervalo_na_origem or clipe.intervalo_na_timeline).duracao /
|
||||
configuracao.intervalo_em_segundos)) + 1
|
||||
inicio_do_clipe = concluidos
|
||||
|
||||
def progresso_atual(atual: int, total: int, nome: str = clipe.nome,
|
||||
base: int = inicio_do_clipe, peso_do_clipe: int = peso) -> None:
|
||||
emitir("Analisando frames", 100 * (base + peso_do_clipe * atual / max(total, 1)) / total_estimado, nome)
|
||||
|
||||
detector = criar_detector_apple_vision(Path(tempfile.gettempdir()) / "premiere-mcp-scanner",
|
||||
configuracao=configuracao)
|
||||
detector.ao_progresso = progresso_atual
|
||||
emitir("Extraindo frames", 100 * inicio_do_clipe / total_estimado, clipe.nome)
|
||||
try:
|
||||
resultado = detector.detectar(clipe)
|
||||
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")))
|
||||
except Exception as erro:
|
||||
resultados.append({"clipe": {"identificador": clipe.identificador, "nome": clipe.nome,
|
||||
"arquivo_original": clipe.arquivo}, "erro": str(erro)})
|
||||
concluidos += peso
|
||||
faixas_de_audio = [faixa for faixa in timeline.faixas
|
||||
if faixa.tipo == "audio" and faixa.indice in configuracao.faixas_de_audio]
|
||||
audio = [{"indice": faixa.indice, "nome": faixa.nome,
|
||||
"clipes": [{"identificador": clipe.identificador, "nome": clipe.nome,
|
||||
"inicio": clipe.intervalo_na_timeline.inicio, "fim": clipe.intervalo_na_timeline.fim}
|
||||
for clipe in faixa.clipes]}
|
||||
for faixa in faixas_de_audio]
|
||||
caso = nome_seguro(timeline.nome)
|
||||
pasta = saida.parent / caso
|
||||
pasta.mkdir(parents=True, exist_ok=True)
|
||||
saida = pasta / f"{caso}-resultado.json"
|
||||
detalhado = pasta / "detalhado"
|
||||
detalhado.mkdir(parents=True, exist_ok=True)
|
||||
geometria = []
|
||||
for resultado in resultados:
|
||||
for frame in resultado.get("observacoes_por_frame", []):
|
||||
fatos = [item for item in frame["evidencias"] if item["tipo"] in {"rosto", "pessoa", "pose", "mao"}]
|
||||
if fatos:
|
||||
geometria.append({"clipe": resultado["clipe"]["identificador"], "timestamp_na_origem": frame["timestamp_na_origem"], "evidencias": fatos})
|
||||
frame["evidencias"] = [item for item in frame["evidencias"] if item not in fatos]
|
||||
(detalhado / "geometria.jsonl").write_text("".join(json.dumps(item, ensure_ascii=False) + "\n" for item in geometria), encoding="utf-8")
|
||||
video_arquivo = f"{caso}-video.json"
|
||||
(pasta / video_arquivo).write_text(json.dumps({"clipes": resultados}, ensure_ascii=False, indent=2), encoding="utf-8")
|
||||
audio_arquivos = []
|
||||
transcricoes_por_clipe: dict[str, list[dict]] = {}
|
||||
transcricao_configurada = pedido["perfil"].get("transcricao", {})
|
||||
if faixas_de_audio and transcricao_configurada.get("caminho_modelo"):
|
||||
try:
|
||||
from engine.integracoes.whisper import ProviderDeTranscricaoLocal
|
||||
from engine.dominio import Timeline
|
||||
emitir("Transcrevendo áudio", 5)
|
||||
provider = ProviderDeTranscricaoLocal(transcricao_configurada["caminho_modelo"],
|
||||
idioma=transcricao_configurada.get("idioma", "pt"))
|
||||
diarizador = None
|
||||
if transcricao_configurada.get("diarizacao"):
|
||||
if not os.environ.get("HF_TOKEN"):
|
||||
transcricao_configurada = {**transcricao_configurada,
|
||||
"aviso_diarizacao": "Token do Hugging Face não configurado."}
|
||||
else:
|
||||
from engine.integracoes.huggingface import ProviderDeDiarizacaoHuggingFace
|
||||
modelo_diarizacao = os.environ.get("HF_DIARIZATION_MODEL", MODELO_DIARIZACAO_PADRAO)
|
||||
diarizador = ProviderDeDiarizacaoHuggingFace(modelo_diarizacao)
|
||||
transcricao_configurada = {**transcricao_configurada,
|
||||
"modelo_diarizacao": modelo_diarizacao}
|
||||
transcricoes = TranscricaoDaTimeline(provider, diretoria_de_trabalho=pasta / ".cache-audio",
|
||||
diarizador=diarizador).executar(
|
||||
Timeline(timeline.identificador, timeline.nome, faixas=faixas_de_audio))
|
||||
for transcricao in transcricoes:
|
||||
transcricoes_por_clipe[transcricao.identificador_do_clipe] = [
|
||||
{"inicio": item.inicio, "fim": item.fim, "texto": item.texto,
|
||||
"confianca": item.confianca, "falante": item.falante}
|
||||
for item in transcricao.segmentos]
|
||||
except Exception as erro:
|
||||
transcricao_configurada = {**transcricao_configurada, "erro": str(erro)}
|
||||
for faixa in audio:
|
||||
arquivo = f"{caso}-audio-faixa-{faixa['indice'] + 1}.json"
|
||||
segmentos = [{"clipe": clipe["identificador"], "segmentos": transcricoes_por_clipe.get(clipe["identificador"], [])}
|
||||
for clipe in faixa["clipes"]]
|
||||
(pasta / arquivo).write_text(json.dumps({"faixa": faixa, "transcricao": transcricao_configurada,
|
||||
"segmentos_por_clipe": segmentos,
|
||||
"status": "concluida" if transcricoes_por_clipe else "indisponivel"},
|
||||
ensure_ascii=False, indent=2), encoding="utf-8")
|
||||
audio_arquivos.append(arquivo)
|
||||
saida.write_text(json.dumps({"versao": 1, "perfil": pedido["perfil"],
|
||||
"sequencia": {"id": timeline.identificador, "nome": timeline.nome},
|
||||
"artefatos": {"audio": audio_arquivos, "video": video_arquivo,
|
||||
"geometria": "detalhado/geometria.jsonl"}}, ensure_ascii=False, indent=2), encoding="utf-8")
|
||||
emitir("Relatório JSON gerado", 100)
|
||||
return saida
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("entrada", type=Path)
|
||||
parser.add_argument("saida", type=Path)
|
||||
args = parser.parse_args()
|
||||
print(json.dumps({"evento": "concluido", "arquivo": str(executar(args.entrada, args.saida))}, ensure_ascii=False))
|
||||
Reference in New Issue
Block a user