feat: Instalado essentia-tensorflow no venv whisperx-transcricao (
- Instalado essentia-tensorflow no venv whisperx-transcricao (usado pelo painel CEP), corrigindo erro "A classificação musical precisa do suporte TensorFlow do Essentia" no catálogo de trilhas. - Ligada a classificação de emoção da fala (ProviderDeEmocaoHuggingFace) ao scanner via nova flag `classificar_emocao`, e adicionado emoção/confiança como critério de desempate secundário no zoom da skill editar-por-voz. - Zoom do caminho Python (aplicar_plano_de_edicao.py) passou a usar adjustment layer numa faixa de vídeo própria, em vez de escalar o clipe inteiro via set_clip_properties — mesma técnica já usada pelo executor .mjs do painel CEP. - Adicionado campo "Carregar por ID" na aba Editar Vídeo do painel CEP (step 2), para carregar um plano do SQLite direto pelo plano_id quando ele não está associado a nenhum video_id conhecido (ex.: plano de um vídeo que este banco nunca escaneou). - Trocado o campo de ID numérico por uma lista suspensa (novo listar_planos_de_edicao.py + RepositorioDePlanos.listar_todos_os_planos) que mostra data/hora e a intenção registrada pela skill em cada plano salvo, para escolher sem decorar o id. Resumo: - 15 arquivos alterados - 2 novos - 13 modificados - 0 removidos 13 files changed, 451 insertions(+), 65 deletions(-) Arquivos: - .jhonny/analises.db - code/cep-plugin/index.html - code/cep-plugin/main.js - code/engine/editor/aplicador_de_plano_de_edicao.py - code/engine/editor/escrita/escrita_no_editor.py - code/engine/executar_scanner.py - code/engine/persistencia/repositorio_de_planos.py - code/engine/scanner/configuracao_visual.py - code/engine/testes/duplos_de_premiere.py - code/engine/testes/test_aplicador_de_plano_de_edicao.py - code/engine/testes/test_escrita_no_editor.py - code/plugins/premiere-pro/skills/editar-por-voz/criterios/01-leitura-do-json.md - code/plugins/premiere-pro/skills/editar-por-voz/criterios/05-zoom.md - code/engine/listar_planos_de_edicao.py - code/engine/testes/test_configuracao_visual_do_scanner.py
This commit is contained in:
+126
-1
@@ -269,7 +269,7 @@ function switchTab(name) {
|
||||
if (name === "models") renderModelList();
|
||||
if (name === "trilhas") trilhasAoAbrir();
|
||||
if (name === "projeto") projetoListarSequencias();
|
||||
if (name === "silence") { syncTranscribeModelSelect(); syncLanguageInfo(); renderizarTiposDeVideoParaEdicao(); }
|
||||
if (name === "silence") { syncTranscribeModelSelect(); syncLanguageInfo(); renderizarTiposDeVideoParaEdicao(); carregarListaDePlanosEditoriais(); }
|
||||
if (name === "settings") renderEditorPersonalities();
|
||||
if (name === "scanner") { renderScannerTranscriptionOptions(); loadScannerProfile(); renderScannerHuggingFaceStatus(); scannerListarAnalisesExistentes(); }
|
||||
if (name === "refinar") refinarAoAbrir();
|
||||
@@ -951,6 +951,8 @@ var ANALISE_STATUS_SCRIPT = "/Volumes/Merongo/SISTEMAS/GENIAL SISTEMAS/Jhonny/co
|
||||
var ANALISE_PREVIEW_SCRIPT = "/Volumes/Merongo/SISTEMAS/GENIAL SISTEMAS/Jhonny/code/engine/consultar_preview_de_falantes.py";
|
||||
var REGISTRAR_EDICAO_SCRIPT = "/Volumes/Merongo/SISTEMAS/GENIAL SISTEMAS/Jhonny/code/engine/registrar_edicao.py";
|
||||
var CARREGAR_PLANO_SCRIPT = "/Volumes/Merongo/SISTEMAS/GENIAL SISTEMAS/Jhonny/code/engine/carregar_plano_de_edicao.py";
|
||||
var LISTAR_PLANOS_SCRIPT = "/Volumes/Merongo/SISTEMAS/GENIAL SISTEMAS/Jhonny/code/engine/listar_planos_de_edicao.py";
|
||||
var planosEditoriaisListados = [];
|
||||
var REGISTRAR_PROJETO_SCRIPT = "/Volumes/Merongo/SISTEMAS/GENIAL SISTEMAS/Jhonny/code/engine/registrar_projeto_de_edicao.py";
|
||||
var CONSULTAR_EDICAO_SCRIPT = "/Volumes/Merongo/SISTEMAS/GENIAL SISTEMAS/Jhonny/code/engine/consultar_edicao_concluida.py";
|
||||
var planoEditorialIdCarregado = null;
|
||||
@@ -3553,6 +3555,129 @@ function carregarPlanoEditorialSalvo() {
|
||||
);
|
||||
}
|
||||
|
||||
// Popula o seletor da etapa 2 com os planos mais recentes de qualquer vídeo
|
||||
// (não só o da sequência detectada) — cada opção mostra data/hora e a
|
||||
// intenção que a skill de edição registrou, para escolher sem decorar o id.
|
||||
function carregarListaDePlanosEditoriais() {
|
||||
var seletor = document.getElementById("editorialPlanIdInput");
|
||||
var detalhe = document.getElementById("editorialPlanIdDetalhe");
|
||||
if (!seletor) return;
|
||||
seletor.innerHTML = "";
|
||||
seletor.appendChild(new Option("Carregando planos…", ""));
|
||||
if (detalhe) detalhe.textContent = "";
|
||||
runStreamed(
|
||||
SCANNER_PYTHON_BIN,
|
||||
[LISTAR_PLANOS_SCRIPT, "--banco", ANALISES_DB_PATH, "--limite", "50"],
|
||||
function () {},
|
||||
function (codigo, ultimoErro, ultimaSaida) {
|
||||
seletor.innerHTML = "";
|
||||
if (codigo !== 0) {
|
||||
seletor.appendChild(new Option("Falha ao listar planos", ""));
|
||||
silenceLog("Não foi possível listar os planos salvos.", "err");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
var resposta = JSON.parse(ultimaSaida || "{}");
|
||||
if (!resposta.ok) throw new Error(resposta.erro || "Resposta inválida do banco.");
|
||||
planosEditoriaisListados = resposta.planos || [];
|
||||
if (!planosEditoriaisListados.length) {
|
||||
seletor.appendChild(new Option("Nenhum plano salvo ainda", ""));
|
||||
return;
|
||||
}
|
||||
seletor.appendChild(new Option("Selecione um plano…", ""));
|
||||
planosEditoriaisListados.forEach(function (plano) {
|
||||
var quando = formatarDataHoraDoPlano(plano.criado_em);
|
||||
var intencaoResumida = plano.intencao
|
||||
? (plano.intencao.length > 70 ? plano.intencao.slice(0, 70) + "…" : plano.intencao)
|
||||
: (plano.origem || "sem intenção registrada");
|
||||
var rotulo = "#" + plano.id + " · " + quando + " · " + intencaoResumida;
|
||||
seletor.appendChild(new Option(rotulo, String(plano.id)));
|
||||
});
|
||||
} catch (erro) {
|
||||
seletor.innerHTML = "";
|
||||
seletor.appendChild(new Option("Falha ao listar planos", ""));
|
||||
silenceLog("Falha ao interpretar a lista de planos: " + erro.message, "err");
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function formatarDataHoraDoPlano(criadoEm) {
|
||||
if (!criadoEm) return "data desconhecida";
|
||||
var data = new Date(criadoEm);
|
||||
if (isNaN(data.getTime())) return criadoEm;
|
||||
return data.toLocaleString("pt-BR", { day: "2-digit", month: "2-digit", hour: "2-digit", minute: "2-digit" });
|
||||
}
|
||||
|
||||
// Ao trocar a seleção, mostra a intenção completa e demais metadados do
|
||||
// plano escolhido — o rótulo do <option> já vem truncado.
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
var seletor = document.getElementById("editorialPlanIdInput");
|
||||
if (!seletor) return;
|
||||
seletor.addEventListener("change", function () {
|
||||
var detalhe = document.getElementById("editorialPlanIdDetalhe");
|
||||
if (!detalhe) return;
|
||||
var plano = planosEditoriaisListados.filter(function (p) { return String(p.id) === seletor.value; })[0];
|
||||
if (!plano) { detalhe.textContent = ""; return; }
|
||||
detalhe.textContent = "Origem: " + (plano.origem || "—") + " · " + plano.total_de_acoes + " ação(ões) · " +
|
||||
(plano.modelo_da_ia || "modelo não registrado") +
|
||||
(plano.intencao ? " — " + plano.intencao : "");
|
||||
});
|
||||
});
|
||||
|
||||
// Busca um plano pelo id escolhido na lista, sem depender do video_id da
|
||||
// sequência detectada. Existe para o caso em que o plano foi registrado para
|
||||
// uma mídia que este banco nunca escaneou (outro projeto/pasta) —
|
||||
// registrar_plano_de_edicao.py aceita gravar sem video_id quando não acha
|
||||
// correspondência em edicoes_de_video, e sem video_id o carregamento
|
||||
// automático por sequência (carregarPlanoEditorialSalvo) nunca encontraria
|
||||
// esse plano.
|
||||
function carregarPlanoEditorialPorId() {
|
||||
var campoId = document.getElementById("editorialPlanIdInput");
|
||||
var planoId = parseInt(campoId.value, 10);
|
||||
if (!planoId || planoId <= 0) {
|
||||
showToast("err", "Selecione um plano na lista antes de carregar.");
|
||||
return;
|
||||
}
|
||||
setBusy("btnLoadPlanById", true);
|
||||
silenceLog("Lendo o plano " + planoId + " diretamente do banco…");
|
||||
runStreamed(
|
||||
SCANNER_PYTHON_BIN,
|
||||
[CARREGAR_PLANO_SCRIPT, "--banco", ANALISES_DB_PATH, "--plano-id", String(planoId)],
|
||||
function (linha) { silenceLog(linha); },
|
||||
function (codigo, ultimoErro, ultimaSaida) {
|
||||
setBusy("btnLoadPlanById", false);
|
||||
if (codigo !== 0) {
|
||||
silenceLog("Não foi possível carregar o plano " + planoId + ".", "err");
|
||||
showToast("err", ultimoErro || "Plano " + planoId + " não encontrado no banco.");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
var resposta = JSON.parse(ultimaSaida || "{}");
|
||||
if (!resposta.ok || !resposta.plano) throw new Error("Resposta inválida do banco.");
|
||||
// Plano sem video_id (ou de outra sequência) não tem como resolver a
|
||||
// mídia real pelo cadastro de clipes — usa a mídia detectada na
|
||||
// sequência ativa só quando o painel já a conhece; senão mantém o
|
||||
// "source" gravado no plano e deixa a engine (que lê a sequência
|
||||
// ativa do Premiere no momento da aplicação) resolver.
|
||||
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.";
|
||||
document.getElementById("editorialSavedPlanStatus").textContent = "Plano " + planoEditorialIdCarregado + " carregado do banco (por ID).";
|
||||
document.getElementById("editorialPlanJsonStatus").textContent = "Plano salvo no SQLite; pronto para aprovação e aplicação.";
|
||||
document.getElementById("btnApplyEditorialActions").disabled = false;
|
||||
setStep(2, "done", "Plano carregado");
|
||||
setStep(3, "ready", "Pronto para aplicar");
|
||||
silenceLog("Plano " + planoEditorialIdCarregado + " carregado diretamente do SQLite (por ID).", "ok");
|
||||
showToast("ok", "Plano " + planoEditorialIdCarregado + " carregado.");
|
||||
} catch (erro) {
|
||||
showToast("err", erro.message);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// Não gera decisões de corte: só empacota transcrição + tempos + locutores +
|
||||
// métricas de voz + configurações de edição num JSON para um agente de IA
|
||||
// externo analisar. Quem decide os cortes é a IA, não este painel.
|
||||
|
||||
Reference in New Issue
Block a user