feat: criado monitoramento contínuo de trilhas com varredura recur

- criado monitoramento contínuo de trilhas com varredura recursiva e baixa prioridade
- gerado e validado plano editorial revisável do depoimento da Erika sem aplicação na timeline
- adicionada leitura e comparação estrutural da timeline após aplicar planos de edição
- limitadas as classificações de trilhas às duas maiores probabilidades de cada categoria
- limpo o catálogo musical para reiniciar as análises sem remover dados de vídeo

Resumo:
- 34 arquivos alterados
- 6 novos
- 28 modificados
- 0 removidos

 28 files changed, 833 insertions(+), 75 deletions(-)

Arquivos:
  - .jhonny/analises.db
  - code/cep-plugin/index.html
  - code/cep-plugin/main.js
  - code/engine/analisar_trilhas.py
  - code/engine/aplicar_plano_de_edicao.py
  - code/engine/consultar_detalhes_de_trilha.py
  - code/engine/editor/__init__.py
  - code/engine/editor/aplicador_de_plano_de_edicao.py
  - code/engine/editor/backup_de_sequencia.py
  - code/engine/editor/escrita/escrita_no_editor.py
  - code/engine/editor/modelos.py
  - code/engine/editor/validacao_semantica.py
  - code/engine/integracoes/audio/classificador_de_musica_essentia.py
  - code/engine/integracoes/audio/modelos_de_analise_musical.py
  - code/engine/integracoes/audio/provider_de_analise_musical_essentia.py
  - code/engine/persistencia/esquema.py
  - code/engine/persistencia/repositorio_de_trilhas.py
  - code/engine/testes/duplos_de_premiere.py
  - code/engine/testes/test_aplicador_de_plano_de_edicao.py
  - code/engine/testes/test_aplicar_plano_de_edicao.py
  - code/engine/testes/test_escrita_no_editor.py
  - code/engine/testes/test_leitor_de_plano_de_edicao.py
  - code/engine/testes/test_validador_semantico_de_plano.py
  - code/plugins/premiere-pro/skills/editar-por-voz/SKILL.md
  - code/plugins/premiere-pro/skills/editar-por-voz/criterios/08-formato-de-saida.md
  - code/plugins/premiere-pro/skills/editar-por-voz/criterios/10-revisao-humana.md
  - code/src/tools/timeline.ts
  - code/tests/tools/tool-modules.test.ts
  - .jhonny/planos/
  - code/.jhonny/analises.db-shm
  - code/.jhonny/analises.db-wal
  - code/engine/editor/verificador_da_aplicacao.py
  - code/engine/testes/test_verificador_da_aplicacao.py
  - code/plugins/premiere-pro/skills/editar-por-voz/criterios/11-gancho.md
This commit is contained in:
João Henrique
2026-09-10 15:09:27 -04:00
parent da400de7d3
commit 2c9bdf2ff0
34 changed files with 1179 additions and 75 deletions
+1 -1
View File
@@ -306,7 +306,7 @@
</div>
<p class="field-help" id="trilhasPastaStatus">O caminho será salvo no banco para reutilizar esta pasta depois.</p>
<p class="field-help">A análise usa os modelos Essentia instalados internamente pela aplicação. Você não precisa localizar nem selecionar essa pasta.</p>
<button class="button button-primary" id="btnTrilhasAnalisar" onclick="trilhasAnalisar()" type="button" disabled>Analisar trilhas</button>
<button class="button button-primary" id="btnTrilhasAnalisar" onclick="trilhasAnalisar()" type="button" disabled>Iniciar monitoramento</button>
</section>
<section class="card" id="trilhasProgressoCard" hidden>
<div class="card-head"><h2>Progresso da análise</h2><span class="card-kicker" id="trilhasContagem">0 trilhas</span></div>
+42 -16
View File
@@ -1511,6 +1511,7 @@ var https = nodeRequire("https");
var childProcess = nodeRequire("child_process");
TRILHAS_BANCO_PATH = path.join(os.homedir(), ".premiere-mcp", "trilhas.db");
TRILHAS_MODELOS_PATH = path.join(os.homedir(), ".premiere-mcp", "modelos-essentia");
if (!fs.existsSync(TRILHAS_MODELOS_PATH) && fs.existsSync("/private/tmp/essentia-modelos")) TRILHAS_MODELOS_PATH = "/private/tmp/essentia-modelos";
function defaultBridgeDirectory() {
try {
var nodeProcess = nodeRequire("process");
@@ -2889,15 +2890,15 @@ function trilhasAoAbrir() {
var pasta = document.getElementById("trilhasPasta");
if (pasta && configuracao.pasta) pasta.value = configuracao.pasta;
trilhasAtualizarEstado();
if (pasta && pasta.value.trim() && fs.existsSync(pasta.value.trim())) trilhasAtualizarCatalogo(pasta.value.trim());
if (pasta && pasta.value.trim() && fs.existsSync(pasta.value.trim())) trilhasAtualizarCatalogo(pasta.value.trim(), function () { trilhasIniciarWorker(pasta.value.trim()); });
}
function trilhasAtualizarCatalogo(pasta) {
function trilhasAtualizarCatalogo(pasta, aoFinalizar) {
var argumentos = [TRILHAS_ENGINE_SCRIPT, pasta, "--banco", TRILHAS_BANCO_PATH, "--listar"];
runStreamed(SCANNER_PYTHON_BIN, argumentos, function (linha) {
if (linha.indexOf("@@PROGRESS ") !== 0) return;
try { trilhasAtualizarEvento(JSON.parse(linha.slice(11))); } catch (_) {}
}, function () {}, { cwd: "/Volumes/Merongo/SISTEMAS/GENIAL SISTEMAS/Jhonny/code", stdio: ["ignore", "pipe", "pipe"] });
}, function () { if (typeof aoFinalizar === "function") aoFinalizar(); }, { cwd: "/Volumes/Merongo/SISTEMAS/GENIAL SISTEMAS/Jhonny/code", stdio: ["ignore", "pipe", "pipe"] });
}
function trilhasListarArquivos(pasta) {
@@ -2938,6 +2939,7 @@ function trilhasSelecionarPasta() {
trilhasSalvarConfiguracao(escolhida);
trilhasAtualizarEstado();
trilhasPrepararLista(escolhida);
trilhasIniciarWorker(escolhida);
} catch (erro) { showToast("err", erro.message); }
}
@@ -2996,9 +2998,11 @@ function trilhasCarregarDetalhes(item, detalhes) {
var etiquetas = dados.etiquetas || [];
var generos = etiquetas.filter(function (etiqueta) { return etiqueta.tipo === "genero"; });
var humores = etiquetas.filter(function (etiqueta) { return etiqueta.tipo === "humor"; });
var temas = etiquetas.filter(function (etiqueta) { return etiqueta.tipo === "tema"; });
function formatar(valor, casas) { return valor === null || valor === undefined ? "—" : Number(valor).toFixed(casas || 2); }
function listaEtiquetas(itens) { return itens.length ? itens.map(function (item) { return item.etiqueta + " (" + formatar(Number(item.confianca) * 100, 1) + "%)"; }).join(" · ") : "Não classificado"; }
detalhes.innerHTML = '<div class="trilha-detalhes-grid"><div><small>Duração</small><strong>' + formatar(dados.duracao_em_segundos, 1) + ' s</strong></div><div><small>BPM</small><strong>' + formatar(dados.batidas_por_minuto, 1) + '</strong></div><div><small>Tonalidade</small><strong>' + (dados.tonalidade || "—") + ' ' + (dados.modo || "") + '</strong></div><div><small>Intensidade</small><strong>' + formatar(dados.intensidade, 2) + '</strong></div><div><small>Dançabilidade</small><strong>' + formatar(dados.dancabilidade, 2) + '</strong></div></div><div class="trilha-etiquetas"><div><small>Estilos</small><p>' + listaEtiquetas(generos) + '</p></div><div><small>Humores</small><p>' + listaEtiquetas(humores) + '</p></div></div><small class="trilha-fonte">Análise salva por ' + (dados.provedor || "Essentia") + (dados.modelo ? " · " + dados.modelo : "") + '</small>';
function listaTextos(itens) { return itens && itens.length ? itens.join(" · ") : "Não identificado"; }
detalhes.innerHTML = '<div class="trilha-detalhes-grid"><div><small>Duração</small><strong>' + formatar(dados.duracao_em_segundos, 1) + ' s</strong></div><div><small>BPM</small><strong>' + formatar(dados.batidas_por_minuto, 1) + '</strong></div><div><small>Tonalidade</small><strong>' + (dados.tonalidade || "—") + ' ' + (dados.modo || "") + '</strong></div><div><small>Intensidade</small><strong>' + formatar(dados.intensidade, 2) + '</strong></div><div><small>Dançabilidade</small><strong>' + formatar(dados.dancabilidade, 2) + '</strong></div></div><div class="trilha-etiquetas"><div><small>Estilos / gêneros</small><p>' + listaEtiquetas(generos) + '</p></div><div><small>Humores</small><p>' + listaEtiquetas(humores) + '</p></div><div><small>Temas</small><p>' + listaEtiquetas(temas) + '</p></div><div><small>Instrumentos</small><p>' + listaTextos(dados.instrumentos) + '</p></div><div><small>Etiquetas do áudio</small><p>' + listaTextos(dados.etiquetas_audio) + '</p></div></div><small class="trilha-fonte">Análise salva por ' + (dados.provedor || "Essentia") + (dados.modelo ? " · " + dados.modelo : "") + '</small>';
detalhes.setAttribute("data-carregado", "true");
} catch (_) { detalhes.textContent = "A resposta da análise está inválida."; }
}
@@ -3008,7 +3012,7 @@ function trilhasAtualizarEvento(evento) {
if (evento.evento === "lote_iniciado") { trilhasRenderizarLista(evento.trilhas || 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];
var item = itens.filter(function (elemento) { return (evento.caminho && elemento.getAttribute("data-caminho") === evento.caminho) || 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);
@@ -3022,19 +3026,29 @@ function trilhasAtualizarEvento(evento) {
function trilhasAnalisar() {
var pasta = document.getElementById("trilhasPasta").value.trim();
if (!pasta || !fs.existsSync(pasta) || !fs.statSync(pasta).isDirectory()) { showToast("err", "Selecione uma pasta existente."); return; }
trilhasIniciarWorker(pasta);
}
function trilhasIniciarWorker(pasta) {
if (trilhasProcesso || taskState.active) return;
trilhasSalvarConfiguracao(pasta);
var argumentos = [TRILHAS_ENGINE_SCRIPT, pasta, "--banco", TRILHAS_BANCO_PATH];
argumentos.push("--modelos", TRILHAS_MODELOS_PATH);
var argumentos = [TRILHAS_ENGINE_SCRIPT, pasta, "--banco", TRILHAS_BANCO_PATH, "--modelos", TRILHAS_MODELOS_PATH, "--continuo", "--intervalo", "20"];
var ambiente = Object.assign({}, process.env, { OMP_NUM_THREADS: "1", OPENBLAS_NUM_THREADS: "1", MKL_NUM_THREADS: "1", TF_NUM_INTRAOP_THREADS: "1", TF_NUM_INTEROP_THREADS: "1" });
setBusy("btnTrilhasAnalisar", true);
taskStart("Analisando trilhas musicais", "Descobrindo arquivos", function (evento) { trilhasAtualizarEvento(evento); }, function () { if (trilhasProcesso) trilhasProcesso.kill("SIGTERM"); });
taskStart("Catálogo de trilhas em segundo plano", "Monitorando a pasta", 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"] });
if (taskState.canceled) { taskEnd(false, "Monitoramento interrompido"); return; }
if (codigo !== 0) { taskEnd(false, "Falha no monitoramento das trilhas", erro || "O processamento não foi concluído."); return; }
taskEnd(true, "Monitoramento das trilhas concluído");
}, { cwd: "/Volumes/Merongo/SISTEMAS/GENIAL SISTEMAS/Jhonny/code", env: ambiente, stdio: ["ignore", "pipe", "pipe"] });
if (trilhasProcesso && trilhasProcesso.pid) childProcess.spawn("renice", ["-n", "15", "-p", String(trilhasProcesso.pid)], { stdio: "ignore" });
}
var trilhasProcesso = null;
window.addEventListener("beforeunload", function () {
if (trilhasProcesso) trilhasProcesso.kill("SIGTERM");
});
// 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).
@@ -3619,7 +3633,7 @@ function resolveEditorialActionsScript() {
return resolveScript("apply-editorial-actions.mjs", EDITORIAL_ACTIONS_APPLY_SCRIPT_FALLBACK);
}
// ---- Aplicar o plano de edição devolvido pela IA (cut/zoom/text/marker) ----
// ---- Aplicar o plano de edição devolvido pela IA (cut/hook/zoom/text/marker) ----
function lerPlanoEditorialDaTela() {
var campoJson = document.getElementById("editorialPlanJson");
var campoCaminho = document.getElementById("editorialPlanPath");
@@ -3647,7 +3661,7 @@ function invalidarVinculoDoPlanoEditorial() {
planoEditorialIdCarregado = null;
var status = document.getElementById("editorialPlanJsonStatus");
if (status) {
status.textContent = "Plano alterado fora do banco. Registre e carregue a nova versão antes de aplicar cortes.";
status.textContent = "Plano alterado fora do banco. Registre e carregue a nova versão antes de aplicar cortes ou ganchos.";
status.className = "field-help err";
}
}
@@ -3669,13 +3683,13 @@ function applyEditorialActions() {
var numActions = plan.actions.length;
var summary =
numActions + " ação(ões) no plano (cortes, zooms, textos e/ou marcadores).\n\n" +
numActions + " ação(ões) no plano (cortes, ganchos, zooms, textos e/ou marcadores).\n\n" +
"Um backup da sequência atual será criado antes de aplicar. Confirmar?";
if (typeof window.confirm === "function" && !window.confirm(summary)) return;
setBusy("btnApplyEditorialActions", true);
setStep(3, "running", "Validando…");
taskStart("Aplicando o plano de edição", "Validando frases e bordas de corte");
taskStart("Aplicando o plano de edição", "Validando frases e bordas editoriais");
var caminhoDoPlano = path.join(silenceState.outputDir || os.tmpdir(), "plano-editorial-" + Date.now() + ".json");
try { fs.writeFileSync(caminhoDoPlano, JSON.stringify(plan, null, 2), "utf-8"); } catch (erroEscrita) {
setBusy("btnApplyEditorialActions", false);
@@ -3703,6 +3717,10 @@ function applyEditorialActions() {
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(" | ");
if (!detalheDaAplicacao && resultadoDaAplicacao.verificacao && resultadoDaAplicacao.verificacao.aprovada === false) {
detalheDaAplicacao = resultadoDaAplicacao.verificacao.mensagem ||
(resultadoDaAplicacao.verificacao.divergencias || []).join(" | ");
}
if (!detalheDaAplicacao && resultadoDaAplicacao.erro) detalheDaAplicacao = resultadoDaAplicacao.erro;
} catch (_) {}
setStep(3, "error", "Bloqueado");
@@ -3712,8 +3730,16 @@ function applyEditorialActions() {
return;
}
setStep(3, "done", "Aplicado");
taskEnd(true, numActions + " ação(ões) aplicadas na timeline!");
var verificacaoDaAplicacao = null;
try { verificacaoDaAplicacao = JSON.parse(lastOutput || "{}").verificacao || null; } catch (_) {}
var textoDaVerificacao = verificacaoDaAplicacao && verificacaoDaAplicacao.mensagem
? " Verificação: " + verificacaoDaAplicacao.mensagem
: "";
taskEnd(true, numActions + " ação(ões) aplicadas na timeline!" + textoDaVerificacao);
silenceLog("Plano de edição aplicado com sucesso!", "ok");
if (verificacaoDaAplicacao && verificacaoDaAplicacao.avisos && verificacaoDaAplicacao.avisos.length) {
silenceLog("Atenção na verificação pós-aplicação: " + verificacaoDaAplicacao.avisos.join(" | "), "err");
}
}
);
}