feat: removido o tamanho do modelo do seletor da tela Editar vídeo
- removido o tamanho do modelo do seletor da tela Editar vídeo - acelerada a transcrição do Scanner com inferência Whisper em lote e cache de hashes - garantido um resultado separado para cada clipe de áudio selecionado no Scanner - adicionado cancelamento do processamento do Scanner pela barra de progresso - exibido estado visual próprio para Scanner cancelado na barra de progresso - criado modo explícito de análise do Scanner para separar som, imagem ou ambos - adicionadas métricas opcionais de fala ao Scanner com análise acústica na engine - corrigido o progresso da transcrição do Scanner durante o processamento do Whisper - refatorado o runner Swift do Apple Vision para executar requests em lote com retry CPU-only, corrigindo falhas de CVPixelBuffer/ANE/OCR - documentado o fluxo de edição de vídeo por voz integrado ao Scanner, banco e engine - formalizado o protocolo de edição por chat com perfil editorial e consultas progressivas - gerado plano JSON de corte para depoimento vertical da Erika no Instagram - esquematizado o fluxo de colar e executar planos da IA pela engine após o Scanner - ajustado o plano para manter somente as três etapas existentes da tela de edição - adicionado processamento incremental de locutores e métricas no Scanner com confirmação de reprocessamento - implementado o plano JSON colado na tela e a persistência idempotente das análises do Scanner Resumo: - 8 arquivos alterados - 1 novos - 7 modificados - 0 removidos 7 files changed, 437 insertions(+), 55 deletions(-) Arquivos: - code/cep-plugin/index.html - code/cep-plugin/main.js - code/cep-plugin/styles.css - code/engine/executar_scanner.py - code/engine/persistencia/repositorio_de_analises_sqlite.py - code/engine/testes/test_persistencia_sqlite.py - docs/PLANO-EDICAO-DE-VIDEO-POR-VOZ.md - code/relatorios/plano-edicao-depoimento-erika-instagram.json
This commit is contained in:
+105
-39
@@ -1108,6 +1108,8 @@ var SCANNER_ALL_RESOURCES = ["faces", "qualidade_facial", "pessoas", "pose", "ma
|
||||
|
||||
var scannerTimeline = null;
|
||||
var scannerReportPath = null;
|
||||
var scannerRelatorioExistente = null;
|
||||
var scannerReprocessamentoPendente = false;
|
||||
var scannerProcesso = null;
|
||||
var scannerCancelSolicitado = false;
|
||||
var SCANNER_ENGINE_SCRIPT = "/Volumes/Merongo/SISTEMAS/GENIAL SISTEMAS/Jhonny/code/engine/executar_scanner.py";
|
||||
@@ -1115,6 +1117,34 @@ var SCANNER_ENGINE_SCRIPT = "/Volumes/Merongo/SISTEMAS/GENIAL SISTEMAS/Jhonny/co
|
||||
// `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.
|
||||
function scannerOutputRoot() { return path.join(os.homedir(), ".premiere-mcp", "scanner"); }
|
||||
function scannerNomeSeguro(nome) { return String(nome || "timeline").replace(/[^A-Za-z0-9._-]+/g, "-").replace(/^-+|-+$/g, "") || "timeline"; }
|
||||
|
||||
function scannerCarregarRelatorioExistente() {
|
||||
scannerRelatorioExistente = null;
|
||||
if (!scannerTimeline || !scannerTimeline.name) return;
|
||||
var caso = scannerNomeSeguro(scannerTimeline.name);
|
||||
var caminho = path.join(scannerOutputRoot(), caso, caso + "-resultado.json");
|
||||
try {
|
||||
if (!fs.existsSync(caminho)) return;
|
||||
var relatorio = JSON.parse(fs.readFileSync(caminho, "utf-8"));
|
||||
if (relatorio.artefatos && relatorio.artefatos.audio && relatorio.artefatos.audio.length) { scannerRelatorioExistente = caminho; scannerReportPath = caminho; }
|
||||
} catch (_) { scannerRelatorioExistente = null; }
|
||||
}
|
||||
|
||||
function scannerAtualizarComplementos() {
|
||||
var relatorio = scannerRelatorioExistente;
|
||||
var arquivoDiarizacao = document.getElementById("btnScannerDiarization");
|
||||
var arquivoMetricas = document.getElementById("btnScannerVoiceFeatures");
|
||||
if (!relatorio) { if (arquivoDiarizacao) arquivoDiarizacao.disabled = true; if (arquivoMetricas) arquivoMetricas.disabled = true; return; }
|
||||
var dados = {};
|
||||
try { dados = JSON.parse(fs.readFileSync(relatorio, "utf-8")); } catch (_) {}
|
||||
var transcricao = dados.perfil && dados.perfil.transcricao || {};
|
||||
var audio = dados.artefatos && dados.artefatos.audio || [];
|
||||
var metricas = !!(dados.perfil && dados.perfil.metricas_de_fala) || !!transcricao.metricas_de_fala;
|
||||
var diarizacao = !!transcricao.diarizacao;
|
||||
if (arquivoDiarizacao) { arquivoDiarizacao.disabled = diarizacao || !loadHfToken(); arquivoDiarizacao.textContent = diarizacao ? "Concluído" : "Processar"; }
|
||||
if (arquivoMetricas) { arquivoMetricas.disabled = metricas || !audio.length; arquivoMetricas.textContent = metricas ? "Concluído" : "Processar"; }
|
||||
}
|
||||
|
||||
function scannerSelectedTracks(kind) { return Array.prototype.slice.call(document.querySelectorAll("[data-scanner-track='" + kind + "']:checked")).map(function (item) { return Number(item.value); }).sort(function (a, b) { return a - b; }); }
|
||||
|
||||
@@ -1215,10 +1245,21 @@ function modelNameFromPath(folder) { var value=String(folder).replace(/.*faster-
|
||||
|
||||
function scannerDetectTracks() {
|
||||
setBusy("btnScannerDetectTracks", true); document.getElementById("scannerTracksHelp").textContent = "Lendo as faixas da sequência ativa…";
|
||||
cs.evalScript("(function(){try{var s=app.project.activeSequence;if(!s)return JSON.stringify({ok:false,error:'Nenhuma sequência ativa.'});function tracks(c,a){for(var i=0;i<c.numTracks;i++){var t=c[i],clips=[];for(var j=0;j<t.clips.numItems;j++){var x=t.clips[j],p=x.projectItem;clips.push({nodeId:x.nodeId,name:x.name,start:Number(x.start.seconds),end:Number(x.end.seconds),inPoint:Number(x.inPoint.seconds),outPoint:Number(x.outPoint.seconds),duration:Number(x.duration.seconds),sourceFile:p&&p.getMediaPath?p.getMediaPath():'',sourceOffline:p&&p.isOffline?p.isOffline():false});}a.push({index:i,name:t.name||('Faixa '+(i+1)),numClips:t.clips.numItems,clips:clips});}}var v=[],a=[];tracks(s.videoTracks,v);tracks(s.audioTracks,a);return JSON.stringify({ok:true,id:s.sequenceID,name:s.name,end:Number(s.end.seconds),videoTracks:v,audioTracks:a});}catch(e){return JSON.stringify({ok:false,error:String(e)});}}())", function(raw) { setBusy("btnScannerDetectTracks", false); var data; try { data=JSON.parse(raw); } catch (_) { data={ok:false,error:"Resposta inválida do Premiere."}; } if (!data.ok) { document.getElementById("scannerTracksHelp").textContent=data.error; showToast("err",data.error); return; } scannerTimeline=data; var saved; try { saved=JSON.parse(localStorage.getItem(SCANNER_PROFILE_KEY)||"null")||{}; } catch (_) { saved={}; } scannerRenderTracks("video",data.videoTracks,saved.faixas_de_video||data.videoTracks.map(function(t){return t.index;})); scannerRenderTracks("audio",data.audioTracks,saved.faixas_de_audio||[]); document.getElementById("scannerTracksHelp").textContent=data.name+": "+data.videoTracks.length+" faixa(s) de vídeo e "+data.audioTracks.length+" de áudio detectadas."; document.getElementById("btnScannerProcess").disabled=false; });
|
||||
cs.evalScript("(function(){try{var s=app.project.activeSequence;if(!s)return JSON.stringify({ok:false,error:'Nenhuma sequência ativa.'});function tracks(c,a){for(var i=0;i<c.numTracks;i++){var t=c[i],clips=[];for(var j=0;j<t.clips.numItems;j++){var x=t.clips[j],p=x.projectItem;clips.push({nodeId:x.nodeId,name:x.name,start:Number(x.start.seconds),end:Number(x.end.seconds),inPoint:Number(x.inPoint.seconds),outPoint:Number(x.outPoint.seconds),duration:Number(x.duration.seconds),sourceFile:p&&p.getMediaPath?p.getMediaPath():'',sourceOffline:p&&p.isOffline?p.isOffline():false});}a.push({index:i,name:t.name||('Faixa '+(i+1)),numClips:t.clips.numItems,clips:clips});}}var v=[],a=[];tracks(s.videoTracks,v);tracks(s.audioTracks,a);return JSON.stringify({ok:true,id:s.sequenceID,name:s.name,end:Number(s.end.seconds),videoTracks:v,audioTracks:a});}catch(e){return JSON.stringify({ok:false,error:String(e)});}}())", function(raw) { setBusy("btnScannerDetectTracks", false); var data; try { data=JSON.parse(raw); } catch (_) { data={ok:false,error:"Resposta inválida do Premiere."}; } if (!data.ok) { document.getElementById("scannerTracksHelp").textContent=data.error; showToast("err",data.error); return; } scannerTimeline=data; scannerCarregarRelatorioExistente(); scannerAtualizarComplementos(); var saved; try { saved=JSON.parse(localStorage.getItem(SCANNER_PROFILE_KEY)||"null")||{}; } catch (_) { saved={}; } scannerRenderTracks("video",data.videoTracks,saved.faixas_de_video||data.videoTracks.map(function(t){return t.index;})); scannerRenderTracks("audio",data.audioTracks,saved.faixas_de_audio||[]); document.getElementById("scannerTracksHelp").textContent=data.name+": "+data.videoTracks.length+" faixa(s) de vídeo e "+data.audioTracks.length+" de áudio detectadas."; document.getElementById("btnScannerProcess").disabled=false; });
|
||||
}
|
||||
|
||||
function scannerProcess() {
|
||||
if (scannerRelatorioExistente) { scannerReprocessamentoPendente = true; document.getElementById("scannerReprocessConfirm").hidden = false; return; }
|
||||
scannerIniciarProcessamento();
|
||||
}
|
||||
|
||||
function scannerConfirmarReprocessamento(confirmado) {
|
||||
document.getElementById("scannerReprocessConfirm").hidden = true;
|
||||
if (confirmado && scannerReprocessamentoPendente) scannerIniciarProcessamento();
|
||||
scannerReprocessamentoPendente = false;
|
||||
}
|
||||
|
||||
function scannerIniciarProcessamento() {
|
||||
if (!scannerTimeline) { showToast("err", "Primeiro detecte as faixas da sequência."); return; }
|
||||
var profile; try { profile=scannerProfileFromScreen(); } catch (error) { showToast("err", error.message); return; }
|
||||
if (profile.modo_de_analise === "som" && !profile.faixas_de_audio.length) { showToast("err", "Selecione ao menos uma faixa de áudio."); return; }
|
||||
@@ -1242,7 +1283,27 @@ function scannerProcess() {
|
||||
});
|
||||
child.stderr.on("data",function(chunk){stderr+=chunk.toString();});
|
||||
child.on("error",function(error){setBusy("btnScannerProcess",false);taskEnd(false,"Scanner indisponível",error.stack||error.message);showToast("err",error.message);});
|
||||
child.on("close",function(code){var foiCancelado=scannerCancelSolicitado;scannerProcesso=null;setBusy("btnScannerProcess",false);if(foiCancelado){taskEnd(false,"Scanner cancelado");return;}if(code!==0||!fs.existsSync(scannerReportPath)){taskEnd(false,"Scanner falhou",stderr||"Não foi possível gerar o relatório.");showToast("err",stderr||"Não foi possível gerar o relatório.");return;}taskEnd(true,"Relatório JSON gerado");document.getElementById("scannerReportPath").textContent=scannerReportPath;document.getElementById("scannerReportCard").hidden=false;showToast("ok","Scanner concluído.");});
|
||||
child.on("close",function(code){var foiCancelado=scannerCancelSolicitado;scannerProcesso=null;setBusy("btnScannerProcess",false);if(foiCancelado){taskEnd(false,"Scanner cancelado");return;}if(code!==0||!fs.existsSync(scannerReportPath)){taskEnd(false,"Scanner falhou",stderr||"Não foi possível gerar o relatório.");showToast("err",stderr||"Não foi possível gerar o relatório.");return;}scannerRelatorioExistente=scannerReportPath;scannerAtualizarComplementos();taskEnd(true,"Relatório JSON gerado");document.getElementById("scannerReportPath").textContent=scannerReportPath;document.getElementById("scannerReportCard").hidden=false;showToast("ok","Scanner concluído.");});
|
||||
}
|
||||
|
||||
function scannerProcessarComplemento(acao) {
|
||||
if (!scannerRelatorioExistente || !scannerTimeline) { showToast("err", "Processe o áudio uma vez antes de complementar este item."); return; }
|
||||
var botao = document.getElementById(acao === "diarizacao" ? "btnScannerDiarization" : "btnScannerVoiceFeatures");
|
||||
setBusy(botao.id, true);
|
||||
var entrada = path.join(scannerOutputRoot(), "pedido-complemento-" + Date.now() + ".json");
|
||||
var perfil; try { perfil = scannerProfileFromScreen(); } catch (error) { setBusy(botao.id, false); showToast("err", error.message); return; }
|
||||
fs.writeFileSync(entrada, JSON.stringify({perfil: perfil, timeline: scannerTimeline}, null, 2), "utf-8");
|
||||
var opcoes = {cwd: "/Volumes/Merongo/SISTEMAS/GENIAL SISTEMAS/Jhonny/code", stdio: ["ignore", "pipe", "pipe"]};
|
||||
var hf = hfSpawnOptions(); if (hf && hf.env) opcoes.env = hf.env;
|
||||
var processo = null;
|
||||
taskStart("Complementando a análise do Scanner", acao === "diarizacao" ? "Detectando locutores" : "Calculando métricas de voz", null, function () {
|
||||
if (processo) processo.kill("SIGTERM");
|
||||
});
|
||||
processo = childProcess.spawn(SCANNER_PYTHON_BIN, [SCANNER_ENGINE_SCRIPT, entrada, scannerRelatorioExistente, "--complemento", acao], opcoes);
|
||||
var erro = "";
|
||||
processo.stderr.on("data", function (bloco) { erro += bloco.toString(); });
|
||||
processo.stdout.on("data", function (bloco) { bloco.toString().split("\n").forEach(function (linha) { try { var evento = JSON.parse(linha); if (evento.evento === "progresso") taskUpdate({stage: evento.etapa, detail: evento.clipe || "", pct: evento.percentual}); } catch (_) {} }); });
|
||||
processo.on("close", function (codigo) { processo = null; setBusy(botao.id, false); if (taskState.canceled) { taskEnd(false, "Complemento cancelado"); return; } if (codigo !== 0) { taskEnd(false, "Falha ao processar o complemento.", erro || "Não foi possível processar este complemento."); showToast("err", erro || "Não foi possível processar este complemento."); return; } scannerAtualizarComplementos(); document.getElementById("scannerReportPath").textContent = scannerRelatorioExistente; document.getElementById("scannerReportCard").hidden = false; taskEnd(true, "Complemento concluído"); showToast("ok", acao === "diarizacao" ? "Locutores processados sem refazer a transcrição." : "Métricas da fala processadas sem refazer a transcrição."); });
|
||||
}
|
||||
|
||||
function scannerOpenReport() { if (!scannerReportPath) return; childProcess.spawn("open",[scannerReportPath],{detached:true,stdio:"ignore"}).unref(); }
|
||||
@@ -2363,6 +2424,7 @@ function runStreamed(bin, args, onLine, onDone, options) {
|
||||
if (buffer.trim()) handleLine(buffer);
|
||||
onDone(code, ultimoErro || null);
|
||||
});
|
||||
return child;
|
||||
}
|
||||
|
||||
function silenceTranscribe() {
|
||||
@@ -2785,38 +2847,44 @@ function resolveEditorialActionsScript() {
|
||||
}
|
||||
|
||||
// ---- Aplicar o plano de edição devolvido pela IA (cut/zoom/text/marker) ----
|
||||
function lerPlanoEditorialDaTela() {
|
||||
var campoJson = document.getElementById("editorialPlanJson");
|
||||
var campoCaminho = document.getElementById("editorialPlanPath");
|
||||
var texto = String(campoJson && campoJson.value || "").trim();
|
||||
var caminho = String(campoCaminho && campoCaminho.value || "").trim();
|
||||
if (!texto && caminho) {
|
||||
if (!fs.existsSync(caminho)) throw new Error("Arquivo do plano não encontrado.");
|
||||
texto = fs.readFileSync(caminho, "utf-8");
|
||||
}
|
||||
if (!texto) throw new Error("Cole o JSON ou escolha um arquivo de plano.");
|
||||
var plano;
|
||||
try { plano = JSON.parse(texto); } catch (erro) { throw new Error("JSON inválido: " + erro.message); }
|
||||
if (!plano || typeof plano !== "object" || !plano.source || !Array.isArray(plano.actions) || !plano.actions.length) {
|
||||
throw new Error("O plano precisa ter source e uma lista não vazia de actions.");
|
||||
}
|
||||
plano.actions.forEach(function (acao, indice) {
|
||||
if (!acao || !acao.kind || typeof acao.start !== "number" || typeof acao.end !== "number" || !acao.reason) {
|
||||
throw new Error("actions[" + indice + "] precisa ter kind, start, end e reason.");
|
||||
}
|
||||
});
|
||||
return plano;
|
||||
}
|
||||
|
||||
function applyEditorialActions() {
|
||||
if (!silenceState.sequenceName) {
|
||||
silenceLog("Detecte o clipe/sequência primeiro (passo 1).", "err");
|
||||
showToast("err", "Detecte o vídeo no passo 1 primeiro.");
|
||||
return;
|
||||
}
|
||||
var planPath = document.getElementById("editorialPlanPath").value.trim();
|
||||
if (!planPath) {
|
||||
silenceLog("Informe o caminho do arquivo do plano (JSON de actions).", "err");
|
||||
showToast("err", "Escolha o arquivo do plano de edição.");
|
||||
return;
|
||||
}
|
||||
if (!fs.existsSync(planPath)) {
|
||||
silenceLog("Arquivo não encontrado: " + planPath, "err");
|
||||
showToast("err", "Arquivo não encontrado: " + path.basename(planPath));
|
||||
return;
|
||||
}
|
||||
|
||||
var plan;
|
||||
try {
|
||||
plan = JSON.parse(fs.readFileSync(planPath, "utf-8"));
|
||||
} catch (e) {
|
||||
silenceLog("Não foi possível ler o JSON do plano: " + e.message, "err");
|
||||
showToast("err", "O arquivo não é um JSON válido.");
|
||||
return;
|
||||
}
|
||||
var numActions = Array.isArray(plan.actions) ? plan.actions.length : 0;
|
||||
if (!numActions) {
|
||||
silenceLog("O plano não tem nenhuma action.", "err");
|
||||
showToast("err", "O plano não tem nenhuma ação.");
|
||||
try { plan = lerPlanoEditorialDaTela(); } catch (erroPlano) {
|
||||
var statusPlano = document.getElementById("editorialPlanJsonStatus");
|
||||
if (statusPlano) { statusPlano.textContent = erroPlano.message; statusPlano.className = "field-help err"; }
|
||||
silenceLog(erroPlano.message, "err");
|
||||
showToast("err", erroPlano.message);
|
||||
return;
|
||||
}
|
||||
var numActions = plan.actions.length;
|
||||
|
||||
var summary =
|
||||
numActions + " ação(ões) no plano (cortes, zooms, textos e/ou marcadores).\n\n" +
|
||||
@@ -2836,26 +2904,24 @@ function applyEditorialActions() {
|
||||
silenceLog("Backup criado.", "ok");
|
||||
}
|
||||
|
||||
var applyScript = resolveEditorialActionsScript();
|
||||
if (!applyScript) {
|
||||
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);
|
||||
setStep(5, "error", "Falhou");
|
||||
taskEnd(false, "Script apply-editorial-actions.mjs não encontrado.");
|
||||
silenceLog(
|
||||
"Script apply-editorial-actions.mjs não encontrado. Rode scripts/install-cep.sh para gravar o repoRoot em " +
|
||||
CONFIG_FILE + ".",
|
||||
"err"
|
||||
);
|
||||
taskEnd(false, "Não foi possível salvar o plano.", erroEscrita.message);
|
||||
return;
|
||||
}
|
||||
var nodeBin = resolveNodeBin();
|
||||
|
||||
silenceLog("Aplicando plano de edição na timeline…");
|
||||
runStreamed(
|
||||
nodeBin,
|
||||
[applyScript, "--plan", planPath, "--sequence", silenceState.sequenceId || silenceState.sequenceName],
|
||||
var processoAplicacao = null;
|
||||
silenceLog("Aplicando plano pela engine Python…");
|
||||
taskStart("Aplicando o plano de edição", "Executando pela engine Python", null, function () {
|
||||
if (processoAplicacao) processoAplicacao.kill("SIGTERM");
|
||||
});
|
||||
processoAplicacao = runStreamed(
|
||||
SCANNER_PYTHON_BIN,
|
||||
[TESTAR_ACOES_ENGINE_SCRIPT, caminhoDoPlano],
|
||||
function (line) { silenceLog(line); },
|
||||
function (code, lastErr) {
|
||||
processoAplicacao = null;
|
||||
setBusy("btnApplyEditorialActions", false);
|
||||
if (code !== 0) {
|
||||
setStep(5, "error", "Falhou");
|
||||
|
||||
Reference in New Issue
Block a user