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:
@@ -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 ============================== -->
|
||||
|
||||
@@ -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, "&").replace(/</g, "<") + "</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 ---------------------------------------------------------------
|
||||
|
||||
@@ -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); }
|
||||
|
||||
|
||||
Reference in New Issue
Block a user