feat: copiada a skill editar-por-voz e seus critérios para as skil

- copiada a skill editar-por-voz e seus critérios para as skills do projeto
- adaptada a skill editar-por-voz para usar o banco SQLite como fonte oficial
- corrigida a cópia do JSON do tipo de vídeo para priorizar clipboard Unicode
- criado carregador único do contexto de edição por voz a partir do SQLite
- criada entrada para registrar planos no SQLite e reaproveitar o mesmo plano na aplicação
- habilitado carregamento do plano editorial diretamente do SQLite na interface
- corrigido o bloqueio visual da etapa de carregamento do plano salvo
- configurado carregamento automático do plano salvo após selecionar a sequência
- documentada a sequência como raiz de retomada do fluxo de edição
- criada a estrutura local para análise de músicas instrumentais com Essentia

Resumo:
- 22 arquivos alterados
- 11 novos
- 11 modificados
- 0 removidos

 11 files changed, 270 insertions(+), 34 deletions(-)

Arquivos:
  - .jhonny/analises.db
  - CONTEXT.md
  - code/cep-plugin/index.html
  - code/cep-plugin/main.js
  - code/engine/README.md
  - code/engine/aplicar_plano_de_edicao.py
  - code/engine/integracoes/audio/__init__.py
  - code/engine/persistencia/consultas.py
  - code/engine/requirements-audio.txt
  - code/engine/testes/test_consultas_de_persistencia.py
  - code/tests/cep-tipos-video-encoding.test.ts
  - code/.jhonny/
  - code/engine/carregar_plano_de_edicao.py
  - code/engine/integracoes/audio/contratos.py
  - code/engine/integracoes/audio/modelos_de_analise_musical.py
  - code/engine/integracoes/audio/provider_de_analise_musical_essentia.py
  - code/engine/preparar_edicao_por_voz.py
  - code/engine/registrar_plano_de_edicao.py
  - code/engine/testes/test_analisador_de_musica_essentia.py
  - code/engine/testes/test_carregar_plano_de_edicao.py
  - code/engine/testes/test_registrar_plano_de_edicao.py
  - code/plugins/premiere-pro/skills/editar-por-voz/
This commit is contained in:
João Henrique
2026-09-10 13:09:01 -04:00
parent eda1b1aadc
commit a9e5b5ff58
33 changed files with 1811 additions and 34 deletions
+4
View File
@@ -156,6 +156,10 @@
</div>
<div class="step-body">
<label class="field-label" for="editorialPlanJson">JSON de ações devolvido pela IA</label>
<div class="button-row">
<button type="button" class="button button-ghost" id="btnLoadSavedEditorialPlan" onclick="carregarPlanoEditorialSalvo()" disabled>Carregar plano salvo no banco</button>
</div>
<p class="field-help" id="editorialSavedPlanStatus">O plano será lido diretamente do banco da edição concluída.</p>
<textarea id="editorialPlanJson" class="editorial-plan-json" rows="10" spellcheck="false" placeholder='Cole aqui o JSON com source e actions.'></textarea>
<p class="field-help" id="editorialPlanJsonStatus">Cole o plano recebido da IA. O painel valida o formato antes de executar.</p>
<label class="field-label" for="editorialPlanPath">Arquivo do plano (JSON devolvido pela IA)</label>
+67 -19
View File
@@ -479,7 +479,7 @@ function testarAcoesExecutar() {
SCANNER_PYTHON_BIN,
[TESTAR_ACOES_ENGINE_SCRIPT, entrada],
function (linha) { testarAcoesLog(linha); },
function (codigo, ultimoErro) {
function (codigo, ultimoErro, ultimaSaida) {
if (botao) botao.disabled = false;
if (codigo !== 0) {
testarAcoesAtualizarStatus("Falha na execução. Consulte os detalhes técnicos.", "err");
@@ -792,19 +792,19 @@ function copiarTextoParaAreaDeTransferencia(texto, mensagem) {
else showToast("err", "Não foi possível copiar o JSON. Selecione e copie manualmente.");
}
// O CEP antigo pode corromper caracteres UTF-8 pela API de clipboard do Chromium.
// O pbcopy recebe bytes UTF-8 diretamente e evita essa conversão intermediária.
copiarComPbcopy(texto, function () {
try {
if (navigator.clipboard && navigator.clipboard.writeText) {
navigator.clipboard.writeText(texto).then(function () {
showToast("ok", mensagem);
}).catch(copiarComSelecaoDoPainel);
return;
}
} catch (_) {}
copiarComSelecaoDoPainel();
}, mensagem);
// A API nativa preserva o texto Unicode no clipboard do CEP/Chromium.
// Use pbcopy apenas quando a API não existir ou falhar.
try {
if (navigator.clipboard && navigator.clipboard.writeText) {
navigator.clipboard.writeText(texto).then(function () {
showToast("ok", mensagem);
}).catch(function () {
copiarComPbcopy(texto, copiarComSelecaoDoPainel, mensagem);
});
return;
}
} catch (_) {}
copiarComPbcopy(texto, copiarComSelecaoDoPainel, mensagem);
}
function copiarComPbcopy(texto, fallback, mensagem) {
@@ -946,6 +946,8 @@ var SCANNER_PYTHON_BIN = "/Volumes/Merongo/SISTEMAS/venvs/whisperx-transcricao/b
var ANALISE_STATUS_SCRIPT = "/Volumes/Merongo/SISTEMAS/GENIAL SISTEMAS/Jhonny/code/engine/consultar_status_da_analise.py";
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 planoEditorialIdCarregado = null;
var retakesCasoAtual = null;
var retakesGrupos = [];
var retakesProcesso = null;
@@ -2699,12 +2701,15 @@ function silenceSelectSequence() {
setStep(1, "done", "Timeline escolhida");
lockStepsFrom(2);
setStep(2, "ready", "Sua vez");
setStep(3, "ready", "Carregar plano salvo");
document.getElementById("btnLoadSavedEditorialPlan").disabled = false;
document.getElementById("btnTranscribe").disabled = false;
document.getElementById("silenceJsonCard").hidden = true;
document.getElementById("jsonFileActions").hidden = true;
document.getElementById("silencePlanInfo").textContent = "";
silenceLog("Timeline escolhida: " + result.sequenceName + " — " + result.mediaPath);
renderCachedTranscripts();
carregarPlanoEditorialSalvo();
}
);
}
@@ -2716,9 +2721,10 @@ function silenceDetectClip() {
// Volta os passos seguintes ao estado travado quando o vídeo (ou a transcrição)
// muda — evita aplicar um plano gerado para outro material.
function lockStepsFrom(first) {
if (first <= 3) planoEditorialIdCarregado = null;
var buttons = {
2: ["btnBuildPlan"],
3: ["btnApplyEditorialActions"],
3: ["btnApplyEditorialActions", "btnLoadSavedEditorialPlan"],
};
for (var n = first; n <= 5; n++) {
setStep(n, "locked", "Bloqueado");
@@ -2815,6 +2821,7 @@ function runStreamed(bin, args, onLine, onDone, options) {
var child = childProcess.spawn(bin, args, options || {});
var buffer = "";
var ultimoErro = "";
var ultimaSaida = "";
// Linhas "@@PROGRESS {json}" são o canal de progresso dos scripts Python/Node:
// vão para a barra no topo em vez de virarem ruído no log.
function handleLine(line) {
@@ -2835,12 +2842,12 @@ function runStreamed(bin, args, onLine, onDone, options) {
if (parts[i].trim()) handleLine(parts[i]);
}
}
child.stdout.on("data", function (d) { flushLines(d.toString()); });
child.stdout.on("data", function (d) { ultimaSaida += d.toString(); flushLines(d.toString()); });
child.stderr.on("data", function (d) { ultimoErro = d.toString().trim() || ultimoErro; flushLines(d.toString()); });
child.on("error", function (err) { onDone(1, "spawn error: " + err.message); });
child.on("close", function (code) {
if (buffer.trim()) handleLine(buffer);
onDone(code, ultimoErro || null);
onDone(code, ultimoErro || null, ultimaSaida);
});
return child;
}
@@ -3288,7 +3295,7 @@ function silenceConcluirEdicao() {
SCANNER_PYTHON_BIN,
[REGISTRAR_EDICAO_SCRIPT, caminhoDoPedido, "--banco", ANALISES_DB_PATH],
function (linha) { silenceLog(linha); },
function (codigo, ultimoErro) {
function (codigo, ultimoErro, ultimaSaida) {
setBusy("btnBuildPlan", false);
if (codigo !== 0) {
setStep(2, "error", "Falhou");
@@ -3299,6 +3306,7 @@ function silenceConcluirEdicao() {
}
setStep(2, "done", "Edição concluída");
setStep(3, "locked", "Aguardando plano da IA");
document.getElementById("btnLoadSavedEditorialPlan").disabled = false;
taskEnd(true, "Edição concluída e salva no banco.");
silenceLog("Edição concluída e salva no banco de análises.", "ok");
showToast("ok", "Edição concluída e salva no banco.");
@@ -3306,11 +3314,49 @@ function silenceConcluirEdicao() {
);
}
// Busca o plano já registrado no SQLite; não cria JSON novo nem duplica plano.
function carregarPlanoEditorialSalvo() {
if (!silenceState.sequenceId) {
showToast("err", "Detecte a sequência antes de carregar o plano.");
return;
}
setBusy("btnLoadSavedEditorialPlan", true);
silenceLog("Lendo o plano editorial diretamente do banco…");
runStreamed(
SCANNER_PYTHON_BIN,
[CARREGAR_PLANO_SCRIPT, "--banco", ANALISES_DB_PATH, "--video-id", String(silenceState.sequenceId)],
function (linha) { silenceLog(linha); },
function (codigo, ultimoErro) {
setBusy("btnLoadSavedEditorialPlan", false);
if (codigo !== 0) {
silenceLog("Não foi possível carregar o plano salvo.", "err");
showToast("err", ultimoErro || "Nenhum plano salvo encontrado para este vídeo.");
return;
}
try {
var resposta = JSON.parse(ultimaSaida || "{}");
if (!resposta.ok || !resposta.plano) throw new Error("Resposta inválida do banco.");
planoEditorialIdCarregado = resposta.plano.plano_id;
document.getElementById("editorialPlanJson").value = JSON.stringify(resposta.plano, null, 2);
document.getElementById("editorialSavedPlanStatus").textContent = "Plano " + planoEditorialIdCarregado + " carregado do banco.";
document.getElementById("editorialPlanJsonStatus").textContent = "Plano salvo no SQLite; pronto para aprovação e aplicação.";
document.getElementById("btnApplyEditorialActions").disabled = false;
setStep(3, "ready", "Plano carregado");
silenceLog("Plano " + planoEditorialIdCarregado + " carregado diretamente do SQLite.", "ok");
showToast("ok", "Plano salvo 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.
function silenceGenerateJson() {
if (!silenceState.transcriptPath) return;
planoEditorialIdCarregado = null;
if (!silenceState.tipoVideo) {
showToast("err", "Selecione o tipo de vídeo antes de gerar o JSON.");
setStep(1, "ready", "Selecione um tipo");
@@ -3457,7 +3503,9 @@ function applyEditorialActions() {
});
processoAplicacao = runStreamed(
SCANNER_PYTHON_BIN,
[TESTAR_ACOES_ENGINE_SCRIPT, caminhoDoPlano],
[TESTAR_ACOES_ENGINE_SCRIPT, caminhoDoPlano].concat(
planoEditorialIdCarregado === null ? [] : ["--plano-id", String(planoEditorialIdCarregado), "--banco", ANALISES_DB_PATH]
),
function (line) { silenceLog(line); },
function (code, lastErr) {
processoAplicacao = null;