diff --git a/.jhonny/analises.db b/.jhonny/analises.db
index 4217284..f940567 100644
Binary files a/.jhonny/analises.db and b/.jhonny/analises.db differ
diff --git a/code/cep-plugin/index.html b/code/cep-plugin/index.html
index 6fcf8b1..8640c6c 100755
--- a/code/cep-plugin/index.html
+++ b/code/cep-plugin/index.html
@@ -34,6 +34,9 @@
+
@@ -292,6 +295,30 @@
Confirme quem são os falantes antes de editar. O preview reproduz somente os trechos identificados de cada pessoa.
diff --git a/code/cep-plugin/main.js b/code/cep-plugin/main.js
index 1e460cb..467c687 100755
--- a/code/cep-plugin/main.js
+++ b/code/cep-plugin/main.js
@@ -245,6 +245,7 @@ function switchTab(name) {
var tabs = [
{ key: "projeto", panel: "tabProjeto", btn: "tabBtnProjeto" },
{ key: "scanner", panel: "tabScanner", btn: "tabBtnScanner" },
+ { key: "trilhas", panel: "tabTrilhas", btn: "tabBtnTrilhas" },
{ key: "refinar", panel: "tabRefinar", btn: "tabBtnRefinar" },
{ key: "silence", panel: "tabSilence", btn: "tabBtnSilence" },
{ key: "retakes", panel: "tabRetakes", btn: "tabBtnRetakes" },
@@ -1146,6 +1147,9 @@ var scannerAnalisesExistentes = [];
var scannerProcesso = null;
var scannerCancelSolicitado = false;
var SCANNER_ENGINE_SCRIPT = "/Volumes/Merongo/SISTEMAS/GENIAL SISTEMAS/Jhonny/code/engine/executar_scanner.py";
+var TRILHAS_ENGINE_SCRIPT = "/Volumes/Merongo/SISTEMAS/GENIAL SISTEMAS/Jhonny/code/engine/analisar_trilhas.py";
+var TRILHAS_BANCO_PATH = "";
+var TRILHAS_CONFIG_KEY = "mcp_trilhas_config_v1";
// `path` e `os` são carregados mais abaixo, junto com o runtime Node do CEP.
// Calcular isso aqui interrompia todo o script antes da inicialização do painel.
@@ -1502,6 +1506,7 @@ var path = nodeRequire("path");
var os = nodeRequire("os");
var https = nodeRequire("https");
var childProcess = nodeRequire("child_process");
+TRILHAS_BANCO_PATH = path.join(os.homedir(), ".premiere-mcp", "analises.db");
function defaultBridgeDirectory() {
try {
var nodeProcess = nodeRequire("process");
@@ -2865,6 +2870,104 @@ function runStreamed(bin, args, onLine, onDone, options) {
return child;
}
+// ---- Catálogo de trilhas musicais -----------------------------------------
+function trilhasCarregarConfiguracao() {
+ try { return JSON.parse(localStorage.getItem(TRILHAS_CONFIG_KEY) || "{}") || {}; } catch (_) { return {}; }
+}
+
+function trilhasSalvarConfiguracao(pasta, modelos) {
+ localStorage.setItem(TRILHAS_CONFIG_KEY, JSON.stringify({ pasta: pasta, modelos: modelos || "" }));
+}
+
+function trilhasAoAbrir() {
+ var configuracao = trilhasCarregarConfiguracao();
+ var pasta = document.getElementById("trilhasPasta");
+ var modelos = document.getElementById("trilhasModelos");
+ if (pasta && configuracao.pasta) pasta.value = configuracao.pasta;
+ if (modelos && configuracao.modelos) modelos.value = configuracao.modelos;
+ trilhasAtualizarEstado();
+}
+
+function trilhasSelecionarPasta() {
+ try {
+ var api = window.cep && window.cep.fs;
+ if (!api || typeof api.showOpenDialog !== "function") throw new Error("Seletor de pastas indisponível.");
+ var resultado = api.showOpenDialog(false, true, "Escolha a pasta de trilhas musicais", "", []);
+ var escolhida = resultado && resultado.data && resultado.data.length ? toFsPath(resultado.data[0]) : "";
+ if (!escolhida) return;
+ document.getElementById("trilhasPasta").value = escolhida;
+ trilhasSalvarConfiguracao(escolhida, document.getElementById("trilhasModelos").value.trim());
+ trilhasAtualizarEstado();
+ } catch (erro) { showToast("err", erro.message); }
+}
+
+function trilhasSelecionarModelos() {
+ try {
+ var api = window.cep && window.cep.fs;
+ if (!api || typeof api.showOpenDialog !== "function") throw new Error("Seletor de pastas indisponível.");
+ var resultado = api.showOpenDialog(false, true, "Escolha a pasta dos modelos Essentia", "", []);
+ var escolhida = resultado && resultado.data && resultado.data.length ? toFsPath(resultado.data[0]) : "";
+ if (!escolhida) return;
+ document.getElementById("trilhasModelos").value = escolhida;
+ trilhasSalvarConfiguracao(document.getElementById("trilhasPasta").value.trim(), escolhida);
+ } catch (erro) { showToast("err", erro.message); }
+}
+
+function trilhasAtualizarEstado() {
+ var pasta = document.getElementById("trilhasPasta");
+ var botao = document.getElementById("btnTrilhasAnalisar");
+ if (!pasta || !botao) return;
+ var valida = pasta.value.trim() && fs.existsSync(pasta.value.trim()) && fs.statSync(pasta.value.trim()).isDirectory();
+ botao.disabled = !valida || taskState.active;
+ document.getElementById("trilhasPastaStatus").textContent = valida ? "Pasta pronta para análise." : "Selecione uma pasta existente.";
+}
+
+function trilhasRenderizarLista(nomes) {
+ var lista = document.getElementById("trilhasLista");
+ lista.innerHTML = "";
+ (nomes || []).forEach(function (nome, indice) {
+ var item = document.createElement("div");
+ item.className = "trilha-item";
+ item.id = "trilha-item-" + indice;
+ item.setAttribute("data-state", "aguardando");
+ item.innerHTML = '
';
+ item.querySelector("strong").textContent = nome;
+ lista.appendChild(item);
+ });
+ document.getElementById("trilhasContagem").textContent = (nomes || []).length + " trilha(s)";
+ document.getElementById("trilhasProgressoCard").hidden = !(nomes || []).length;
+}
+
+function trilhasAtualizarEvento(evento) {
+ if (evento.evento === "lote_iniciado") { trilhasRenderizarLista(evento.nomes || []); return; }
+ if (!evento.nome) return;
+ var itens = Array.prototype.slice.call(document.querySelectorAll(".trilha-item"));
+ var item = itens.filter(function (elemento) { return elemento.querySelector("strong").textContent === evento.nome; })[0];
+ if (!item) return;
+ var estado = evento.evento === "trilha_concluida" ? "concluida" : evento.evento === "trilha_com_erro" ? "erro" : "analisando";
+ item.setAttribute("data-state", estado);
+ item.querySelector("span:not(.trilha-check)").textContent = estado === "concluida" ? "Analisada" : estado === "erro" ? "Erro: " + (evento.erro || "falha") : "Analisando…";
+ if (typeof evento.percentual === "number") item.querySelector(".progress-fill").style.width = evento.percentual + "%";
+}
+
+function trilhasAnalisar() {
+ var pasta = document.getElementById("trilhasPasta").value.trim();
+ var modelos = document.getElementById("trilhasModelos").value.trim();
+ if (!pasta || !fs.existsSync(pasta) || !fs.statSync(pasta).isDirectory()) { showToast("err", "Selecione uma pasta existente."); return; }
+ trilhasSalvarConfiguracao(pasta, modelos);
+ var argumentos = [TRILHAS_ENGINE_SCRIPT, pasta, "--banco", TRILHAS_BANCO_PATH];
+ if (modelos) argumentos.push("--modelos", modelos);
+ setBusy("btnTrilhasAnalisar", true);
+ taskStart("Analisando trilhas musicais", "Descobrindo arquivos", function (evento) { trilhasAtualizarEvento(evento); }, function () { if (trilhasProcesso) trilhasProcesso.kill("SIGTERM"); });
+ trilhasProcesso = runStreamed(SCANNER_PYTHON_BIN, argumentos, function () {}, function (codigo, erro) {
+ trilhasProcesso = null; setBusy("btnTrilhasAnalisar", false); trilhasAtualizarEstado();
+ if (codigo !== 0) { taskEnd(false, "Falha na análise das trilhas", erro || "O processamento não foi concluído."); return; }
+ taskEnd(true, "Análise das trilhas concluída");
+ }, { cwd: "/Volumes/Merongo/SISTEMAS/GENIAL SISTEMAS/Jhonny/code", stdio: ["ignore", "pipe", "pipe"] });
+}
+
+var trilhasProcesso = null;
+
// LEGADO comentado em 2026-09-09: transcrição/diarização feitas direto nesta aba,
// duplicando o que a aba Scanner já faz e persiste no banco (.jhonny/analises.db).
// Confirmado por teste real que o Scanner cobre o mesmo fluxo de ponta a ponta.
@@ -3348,6 +3451,9 @@ function carregarPlanoEditorialSalvo() {
try {
var resposta = JSON.parse(ultimaSaida || "{}");
if (!resposta.ok || !resposta.plano) throw new Error("Resposta inválida do banco.");
+ // O plano histórico pode usar o nome da sequência como origem.
+ // A engine localiza os clipes pela mídia real descoberta no Premiere.
+ if (silenceState.mediaPath) resposta.plano.source = silenceState.mediaPath;
planoEditorialIdCarregado = resposta.plano.plano_id;
document.getElementById("editorialPlanJson").value = JSON.stringify(resposta.plano, null, 2);
document.getElementById("planoResumoEdicao").textContent = "Plano " + planoEditorialIdCarregado + " encontrado · " + resposta.plano.actions.length + " ação(ões) · aguardando aprovação.";
@@ -3521,13 +3627,20 @@ function applyEditorialActions() {
planoEditorialIdCarregado === null ? [] : ["--plano-id", String(planoEditorialIdCarregado), "--banco", ANALISES_DB_PATH]
),
function (line) { silenceLog(line); },
- function (code, lastErr) {
+ function (code, lastErr, lastOutput) {
processoAplicacao = null;
setBusy("btnApplyEditorialActions", false);
if (code !== 0) {
+ var detalheDaAplicacao = "";
+ try {
+ var resultadoDaAplicacao = JSON.parse(lastOutput || "{}");
+ var falhasDaAplicacao = (resultadoDaAplicacao.acoes || []).filter(function (acao) { return acao.sucesso === false; });
+ if (falhasDaAplicacao.length) detalheDaAplicacao = falhasDaAplicacao.map(function (acao) { return acao.detalhe; }).join(" | ");
+ } catch (_) {}
setStep(3, "error", "Falhou");
- taskEnd(false, "Falha ao aplicar o plano (código " + code + ").", lastErr);
- silenceLog("Falha ao aplicar o plano (código " + code + ").", "err");
+ var mensagemDaFalha = detalheDaAplicacao || lastErr || "A engine não retornou detalhes.";
+ taskEnd(false, "Falha ao aplicar o plano (código " + code + ").", mensagemDaFalha);
+ silenceLog("Falha ao aplicar o plano (código " + code + "): " + mensagemDaFalha, "err");
return;
}
setStep(3, "done", "Aplicado");
diff --git a/code/engine/analisar_trilhas.py b/code/engine/analisar_trilhas.py
index 2a2beaf..3d9e34a 100644
--- a/code/engine/analisar_trilhas.py
+++ b/code/engine/analisar_trilhas.py
@@ -35,7 +35,7 @@ def executar(pasta: Path, banco: Path, diretorio_dos_modelos: Path | None) -> in
trilhas = repositorio.registrar_pasta_e_arquivos(pasta, arquivos)
classificador = ClassificadorDeMusicaEssentia(diretorio_dos_modelos) if diretorio_dos_modelos else None
analisador = AnalisadorDeMusicaInstrumentalEssentia(classificador=classificador)
- emitir("lote_iniciado", total=len(trilhas), pasta=str(pasta))
+ emitir("lote_iniciado", total=len(trilhas), pasta=str(pasta), nomes=[trilha.nome_do_arquivo for trilha in trilhas])
for ordem, trilha in enumerate(trilhas, start=1):
repositorio.iniciar_analise(trilha.identificador)
emitir("trilha_iniciada", nome=trilha.nome_do_arquivo, indice=ordem, total=len(trilhas), percentual=(ordem - 1) * 100 / max(1, len(trilhas)))