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:
João Henrique
2026-09-08 19:35:30 -04:00
parent 400c3c93dd
commit 4308c72441
24 changed files with 1591 additions and 8 deletions
+22
View File
@@ -31,6 +31,9 @@
<button class="tab-button" id="tabBtnRetakes" role="tab" aria-selected="false" aria-controls="tabRetakes" onclick="switchTab('retakes')" type="button">
<span class="tab-icon" aria-hidden="true">↻</span>Retakes
</button>
<button class="tab-button" id="tabBtnAnalises" role="tab" aria-selected="false" aria-controls="tabAnalises" onclick="switchTab('analises')" type="button">
<span class="tab-icon" aria-hidden="true">⎘</span>Análises
</button>
<button class="tab-button" id="tabBtnBridge" role="tab" aria-selected="false" aria-controls="tabBridge" onclick="switchTab('bridge')" type="button">
<span class="tab-icon" aria-hidden="true">⇄</span>Conexão
</button>
@@ -333,6 +336,25 @@
<div id="retakesLog" role="log" aria-live="polite" aria-relevant="additions" tabindex="0" aria-label="Log de retakes"></div>
</div>
<!-- ============================== ANÁLISES ============================= -->
<div class="tab-panel" id="tabAnalises" role="tabpanel" aria-labelledby="tabBtnAnalises" hidden>
<p class="tab-intro">Lê direto do banco de análises o J-SOM de imagem (evidências visuais) e o de som (transcrição por cena). Nada é reprocessado.</p>
<section class="card">
<div class="card-head"><h2>Banco de análises</h2><span class="card-kicker" id="analisesStatus">—</span></div>
<p class="field-help" id="analisesCaminho">—</p>
<div class="button-row">
<button class="button button-primary" id="btnAnalisesCarregar" onclick="analisesCarregar()" type="button">Carregar do banco</button>
<button class="button button-ghost" onclick="analisesAbrirJson()" type="button" disabled id="btnAnalisesAbrir">Abrir JSON</button>
<button class="button button-ghost" onclick="analisesCopiar()" type="button" disabled id="btnAnalisesCopiar">Copiar conteúdo</button>
</div>
</section>
<section class="card" id="analisesResumoCard" hidden>
<div class="card-head"><h2 id="analisesVideoNome">—</h2><span class="card-kicker" id="analisesResumo">—</span></div>
<p class="field-help">Som: transcrição com emoção por fala. Imagem: evidências Apple Vision por clipe. “Cena” sem marca indica clipe sem corte de cena gravado.</p>
<div id="analisesCenas" role="list" aria-label="Cenas com transcrição e evidências"></div>
</section>
</div>
<!-- ============================== CONEXÃO ============================== -->
+93
View File
@@ -206,6 +206,7 @@ function switchTab(name) {
{ key: "silence", panel: "tabSilence", btn: "tabBtnSilence" },
{ key: "scanner", panel: "tabScanner", btn: "tabBtnScanner" },
{ key: "retakes", panel: "tabRetakes", btn: "tabBtnRetakes" },
{ key: "analises", panel: "tabAnalises", btn: "tabBtnAnalises" },
{ key: "bridge", panel: "tabBridge", btn: "tabBtnBridge" },
{ key: "models", panel: "tabModels", btn: "tabBtnModels" },
{ key: "settings", panel: "tabSettings", btn: "tabBtnSettings" },
@@ -224,6 +225,98 @@ function switchTab(name) {
if (name === "settings") renderEditorPersonalities();
if (name === "scanner") { renderScannerTranscriptionOptions(); loadScannerProfile(); renderScannerHuggingFaceStatus(); }
if (name === "retakes") retakesAoAbrir();
if (name === "analises") analisesAoAbrir();
}
// ---- Análises ---------------------------------------------------------------
// Aba que lê o banco de análises (.jhonny/analises.db) já produzido pelo
// Scanner e mostra/copiar os dois J-SOM: som (transcrição por cena) e
// imagem (evidências visuais). A leitura acontece na engine, via script
// dedicado; a aba nunca escreve no banco.
var ANALISES_ENGINE_SCRIPT = "/Volumes/Merongo/SISTEMAS/GENIAL SISTEMAS/Jhonny/code/engine/gerar_relatorio_de_analises.py";
var ANALISES_DB_PATH = "/Volumes/Merongo/SISTEMAS/GENIAL SISTEMAS/Jhonny/.jhonny/analises.db";
var analisesRelatorioPath = null;
function analisesAoAbrir() {
document.getElementById("analisesCaminho").textContent = ANALISES_DB_PATH;
}
function analisesFormatarTempo(segundos) {
var s = Number(segundos) || 0;
var min = Math.floor(s / 60), resto = (s - min * 60).toFixed(1);
return min + ":" + (resto < 10 ? "0" : "") + resto;
}
function analisesCarregar() {
var stamp = new Date().toISOString().replace(/[:.]/g, "-");
analisesRelatorioPath = path.join(os.tmpdir(), "jhonny-analises-" + stamp + ".json");
setBusy("btnAnalisesCarregar", true); taskStart("Análises", "Lendo o banco de análises", null);
var child = childProcess.spawn(SCANNER_PYTHON_BIN, [ANALISES_ENGINE_SCRIPT, ANALISES_DB_PATH, analisesRelatorioPath],
{ cwd: "/Volumes/Merongo/SISTEMAS/GENIAL SISTEMAS/Jhonny/code", stdio: ["ignore", "pipe", "pipe"] });
var stderr = "", stdout = "";
child.stdout.on("data", function (chunk) { stdout += chunk.toString(); });
child.stderr.on("data", function (chunk) { stderr += chunk.toString(); });
child.on("error", function (error) { setBusy("btnAnalisesCarregar", false); taskEnd(false, "Análises indisponíveis", error.stack || error.message); });
child.on("close", function (code) {
setBusy("btnAnalisesCarregar", false);
if (code !== 0 || !fs.existsSync(analisesRelatorioPath)) {
taskEnd(false, "Leitura do banco falhou", stderr || stdout || "Não foi possível gerar o relatório.");
return;
}
taskEnd(true, "Relatório carregado do banco");
analisesRender(JSON.parse(fs.readFileSync(analisesRelatorioPath, "utf-8")));
});
}
function analisesRender(relatorio) {
var video = relatorio.video || {};
var cenas = relatorio.cenas || [];
var falas = cenas.reduce(function (n, c) { return n + (c.falas || []).length; }, 0);
var evidencias = cenas.reduce(function (n, c) {
var resumo = c.resumo_de_evidencias || {};
return n + Object.keys(resumo).reduce(function (m, k) { return m + resumo[k]; }, 0);
}, 0);
document.getElementById("analisesStatus").textContent = "Carregado";
document.getElementById("analisesVideoNome").textContent = video.nome || "(sem nome)";
document.getElementById("analisesResumo").textContent =
cenas.length + " cenas · " + falas + " falas · " + evidencias + " evidências visuais";
document.getElementById("analisesResumoCard").hidden = false;
document.getElementById("btnAnalisesAbrir").disabled = false;
document.getElementById("btnAnalisesCopiar").disabled = false;
var lista = document.getElementById("analisesCenas");
lista.innerHTML = "";
cenas.forEach(function (cena) {
var bloco = document.createElement("div");
bloco.className = "analise-cena";
bloco.setAttribute("role", "listitem");
var titulo = cena.cena_registrada ? "" : " (sem cena gravada)";
var tipos = Object.keys(cena.resumo_de_evidencias || {})
.sort().map(function (k) { return k + " " + cena.resumo_de_evidencias[k]; }).join(", ") || "—";
var falasHtml = (cena.falas || []).map(function (fala) {
return "<p class=\"analise-fala\"><strong>" + (fala.emocao || "—") + "</strong> " +
analisesFormatarTempo(fala.inicio) + "–" + analisesFormatarTempo(fala.fim) + " · " +
String(fala.texto || "").replace(/&/g, "&amp;").replace(/</g, "&lt;") + "</p>";
}).join("") || "<p class=\"analise-fala analise-vazia\">Sem fala neste clipe.</p>";
bloco.innerHTML =
"<div class=\"analise-cena-cab\"><strong>" + cena.clipe_id + titulo + "</strong>" +
"<span>" + analisesFormatarTempo(cena.inicio) + "–" + analisesFormatarTempo(cena.fim) + "</span></div>" +
falasHtml + "<p class=\"analise-evidencias\">Imagem: " + tipos + "</p>";
lista.appendChild(bloco);
});
}
function analisesAbrirJson() {
if (!analisesRelatorioPath) return;
childProcess.spawn("open", [analisesRelatorioPath], { detached: true, stdio: "ignore" }).unref();
}
function analisesCopiar() {
if (!analisesRelatorioPath) return;
try {
var copy = childProcess.spawn("pbcopy");
copy.stdin.end(fs.readFileSync(analisesRelatorioPath, "utf-8"));
showToast("ok", "J-SOM de som e imagem copiados (JSON completo).");
} catch (error) { showToast("err", error.message); }
}
// ---- Retakes ---------------------------------------------------------------
+11
View File
@@ -612,3 +612,14 @@ button:focus-visible, input:focus-visible, select:focus-visible, textarea:focus-
.button, .path-field input, .select-field, #log, #silenceLog, #modelsLog, .status-panel, .step, .card { border-color: CanvasText; }
.progress-fill { background: Highlight; }
}
/* ---- Aba Análises ------------------------------------------------------- */
.analise-cena { border-top: 1px solid var(--border); padding: 8px 0; }
.analise-cena:first-child { border-top: 0; }
.analise-cena-cab { display: flex; justify-content: space-between; gap: 8px; color: var(--text); font-size: 11px; }
.analise-cena-cab span { color: var(--text-muted); font-variant-numeric: tabular-nums; }
.analise-fala { margin: 4px 0 0; font-size: 11px; color: var(--text); line-height: 1.35; }
.analise-fala strong { display: inline-block; min-width: 34px; color: var(--text-muted); font-weight: 600; text-transform: uppercase; }
.analise-fala.analise-vazia { color: var(--text-muted); font-style: italic; }
.analise-evidencias { margin: 6px 0 0; font-size: 10px; color: var(--text-muted); }