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:
@@ -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
@@ -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");
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user