feat: adicionado atalho para abrir a pasta e copiar o caminho do J
- adicionado atalho para abrir a pasta e copiar o caminho do J-SOM gerado - criada a tela Refinar para identificar falantes e ouvir seus trechos de áudio Resumo: - 3 arquivos alterados - 0 novos - 3 modificados - 0 removidos 3 files changed, 145 insertions(+), 15 deletions(-) Arquivos: - code/cep-plugin/index.html - code/cep-plugin/main.js - code/cep-plugin/styles.css
This commit is contained in:
@@ -28,6 +28,9 @@
|
||||
<button class="tab-button active" id="tabBtnScanner" role="tab" aria-selected="true" aria-controls="tabScanner" onclick="switchTab('scanner')" type="button">
|
||||
<span class="tab-icon" aria-hidden="true">◉</span>Scanner
|
||||
</button>
|
||||
<button class="tab-button" id="tabBtnRefinar" role="tab" aria-selected="false" aria-controls="tabRefinar" onclick="switchTab('refinar')" type="button">
|
||||
<span class="tab-icon" aria-hidden="true">◌</span>Refinar
|
||||
</button>
|
||||
<button class="tab-button" id="tabBtnRetakes" role="tab" aria-selected="false" aria-controls="tabRetakes" onclick="switchTab('retakes')" type="button">
|
||||
<span class="tab-icon" aria-hidden="true">↻</span>Retakes
|
||||
</button>
|
||||
@@ -229,6 +232,10 @@
|
||||
<div><dt>Trechos</dt><dd id="jsonSegmentCount">—</dd></div>
|
||||
<div><dt>Arquivo</dt><dd class="kv-path" id="jsonOutputPath">—</dd></div>
|
||||
</dl>
|
||||
<div class="button-row json-file-actions" id="jsonFileActions" hidden>
|
||||
<button class="button button-ghost" id="btnOpenJsonFolder" onclick="abrirPastaDoJsonGerado()" type="button">Abrir pasta no Finder</button>
|
||||
<button class="button button-ghost" id="btnCopyJsonPath" onclick="copiarCaminhoDoJsonGerado()" type="button">Copiar caminho</button>
|
||||
</div>
|
||||
<p class="field-help" id="silencePlanInfo"></p>
|
||||
</div>
|
||||
</section>
|
||||
@@ -342,6 +349,28 @@
|
||||
<div class="callout callout-info" id="scannerReportCard" hidden><strong>Relatório pronto</strong><p id="scannerReportPath"></p><div class="button-row"><button class="button button-ghost" onclick="scannerOpenReport()" type="button">Abrir JSON</button><button class="button button-ghost" onclick="scannerCopyReport()" type="button">Copiar conteúdo</button></div></div>
|
||||
</section>
|
||||
</div>
|
||||
<!-- =============================== REFINAR ============================== -->
|
||||
<div class="tab-panel" id="tabRefinar" role="tabpanel" aria-labelledby="tabBtnRefinar" hidden>
|
||||
<p class="tab-intro">Confirme quem são os falantes antes de editar. O áudio reproduz somente os trechos identificados de cada pessoa.</p>
|
||||
<section class="card scanner-analysis-card">
|
||||
<div class="card-head"><h2>Falantes da análise</h2><span class="step-chip" id="refinarStatus">Aguardando análise</span></div>
|
||||
<p class="field-help" id="refinarInfo">Carregue a análise mais recente do Scanner para consultar os falantes detectados.</p>
|
||||
<div class="refinar-actions">
|
||||
<button id="btnRefinarLoad" class="button button-primary" onclick="refinarCarregarAnalise()" type="button">Carregar análise do Scanner</button>
|
||||
<button id="btnRefinarStopAudio" class="button" onclick="stopSpeakerAudio()" type="button">Parar áudio</button>
|
||||
</div>
|
||||
</section>
|
||||
<section class="card" id="refinarSpeakerCard">
|
||||
<div class="card-head"><h2>Identificar falantes</h2><span class="card-kicker" id="refinarSpeakerCount">Nenhum falante carregado</span></div>
|
||||
<p class="field-help">Use o botão de reprodução para reconhecer a voz, informe um nome e escolha quem poderá aparecer no vídeo. A identificação será usada ao gerar o JSON de edição.</p>
|
||||
<div class="callout callout-info" id="refinarSpeakerEmpty"><strong>Nenhum falante identificado</strong><p>A análise carregada não possui diarização. Execute a diarização no Scanner para liberar esta etapa.</p></div>
|
||||
<div class="speaker-config" id="refinarSpeakerConfig" hidden>
|
||||
<div id="refinarSpeakerList"></div>
|
||||
<button id="btnRefinarSave" class="button button-primary" onclick="refinarSalvarFalantes()" type="button">Salvar identificação</button>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<!-- ============================== RETAKES ============================== -->
|
||||
<div class="tab-panel" id="tabRetakes" role="tabpanel" aria-labelledby="tabBtnRetakes" hidden>
|
||||
<p class="tab-intro">Identifica repetições e tentativas alternativas usando a transcrição do Scanner. O painel nunca corta o vídeo: você revisa e decide.</p>
|
||||
|
||||
+111
-15
@@ -245,6 +245,7 @@ function switchTab(name) {
|
||||
var tabs = [
|
||||
{ key: "silence", panel: "tabSilence", btn: "tabBtnSilence" },
|
||||
{ key: "scanner", panel: "tabScanner", btn: "tabBtnScanner" },
|
||||
{ key: "refinar", panel: "tabRefinar", btn: "tabBtnRefinar" },
|
||||
{ key: "retakes", panel: "tabRetakes", btn: "tabBtnRetakes" },
|
||||
{ key: "analises", panel: "tabAnalises", btn: "tabBtnAnalises" },
|
||||
{ key: "tipos-video", panel: "tabTiposVideo", btn: "tabBtnTiposVideo" },
|
||||
@@ -267,6 +268,7 @@ function switchTab(name) {
|
||||
if (name === "silence") { syncTranscribeModelSelect(); syncLanguageInfo(); }
|
||||
if (name === "settings") renderEditorPersonalities();
|
||||
if (name === "scanner") { renderScannerTranscriptionOptions(); loadScannerProfile(); renderScannerHuggingFaceStatus(); scannerListarAnalisesExistentes(); }
|
||||
if (name === "refinar") refinarAoAbrir();
|
||||
if (name === "retakes") retakesAoAbrir();
|
||||
if (name === "analises") analisesAoAbrir();
|
||||
if (name === "tipos-video") tiposVideoAoAbrir();
|
||||
@@ -771,7 +773,8 @@ function tipoVideoCopiarJson() {
|
||||
} catch (erro) { showToast("err", erro.message); }
|
||||
}
|
||||
|
||||
function copiarTextoParaAreaDeTransferencia(texto) {
|
||||
function copiarTextoParaAreaDeTransferencia(texto, mensagem) {
|
||||
mensagem = mensagem || "Conteúdo copiado para a área de transferência.";
|
||||
function copiarComSelecaoDoPainel() {
|
||||
var area = document.createElement("textarea");
|
||||
area.value = texto;
|
||||
@@ -785,7 +788,7 @@ function copiarTextoParaAreaDeTransferencia(texto) {
|
||||
var copiado = false;
|
||||
try { copiado = document.execCommand("copy"); } catch (_) { copiado = false; }
|
||||
document.body.removeChild(area);
|
||||
if (copiado) showToast("ok", "JSON resumido copiado para a área de transferência.");
|
||||
if (copiado) showToast("ok", mensagem);
|
||||
else showToast("err", "Não foi possível copiar o JSON. Selecione e copie manualmente.");
|
||||
}
|
||||
|
||||
@@ -795,23 +798,23 @@ function copiarTextoParaAreaDeTransferencia(texto) {
|
||||
try {
|
||||
if (navigator.clipboard && navigator.clipboard.writeText) {
|
||||
navigator.clipboard.writeText(texto).then(function () {
|
||||
showToast("ok", "JSON resumido copiado para a área de transferência.");
|
||||
showToast("ok", mensagem);
|
||||
}).catch(copiarComSelecaoDoPainel);
|
||||
return;
|
||||
}
|
||||
} catch (_) {}
|
||||
copiarComSelecaoDoPainel();
|
||||
});
|
||||
}, mensagem);
|
||||
}
|
||||
|
||||
function copiarComPbcopy(texto, fallback) {
|
||||
function copiarComPbcopy(texto, fallback, mensagem) {
|
||||
try {
|
||||
var copia = childProcess.spawn("pbcopy");
|
||||
var finalizado = false;
|
||||
function concluir(copiado) {
|
||||
if (finalizado) return;
|
||||
finalizado = true;
|
||||
if (copiado) showToast("ok", "JSON resumido copiado para a área de transferência.");
|
||||
if (copiado) showToast("ok", mensagem || "Conteúdo copiado para a área de transferência.");
|
||||
else fallback();
|
||||
}
|
||||
copia.once("error", function () { concluir(false); });
|
||||
@@ -912,6 +915,26 @@ function analisesCopiar() {
|
||||
} catch (error) { showToast("err", error.message); }
|
||||
}
|
||||
|
||||
function abrirPastaDoJsonGerado() {
|
||||
if (!silenceState.jsonOutputPath) return;
|
||||
try {
|
||||
childProcess.spawn("open", [path.dirname(silenceState.jsonOutputPath)], { detached: true, stdio: "ignore" }).unref();
|
||||
} catch (erro) {
|
||||
showToast("err", "Não foi possível abrir a pasta no Finder: " + erro.message);
|
||||
}
|
||||
}
|
||||
|
||||
function copiarCaminhoDoJsonGerado() {
|
||||
if (!silenceState.jsonOutputPath) return;
|
||||
copiarComPbcopy(
|
||||
silenceState.jsonOutputPath,
|
||||
function () {
|
||||
copiarTextoParaAreaDeTransferencia(silenceState.jsonOutputPath, "Caminho do JSON copiado para a área de transferência.");
|
||||
},
|
||||
"Caminho do JSON copiado para a área de transferência."
|
||||
);
|
||||
}
|
||||
|
||||
// ---- Retakes ---------------------------------------------------------------
|
||||
// A aba apenas controla o processo e mostra resultados; a análise inteira
|
||||
// acontece na engine (scanner/retakes), chamada via executar_retakes.py.
|
||||
@@ -2169,9 +2192,11 @@ function silenceLoadScannerAnalysis() {
|
||||
var status = document.getElementById("scannerAnalysisStatus");
|
||||
var info = document.getElementById("scannerAnalysisInfo");
|
||||
setBusy("btnLoadScannerAnalysis", true);
|
||||
setBusy("btnRefinarLoad", true);
|
||||
var caminhoDoRelatorio = silenceLocalizarUltimoRelatorioDoScanner();
|
||||
if (!caminhoDoRelatorio) {
|
||||
setBusy("btnLoadScannerAnalysis", false);
|
||||
setBusy("btnRefinarLoad", false);
|
||||
status.textContent = "Não encontrada";
|
||||
info.textContent = "Execute o Scanner primeiro para criar uma análise reutilizável.";
|
||||
showToast("err", info.textContent);
|
||||
@@ -2199,7 +2224,8 @@ function silenceLoadScannerAnalysis() {
|
||||
var turnos = transcricao.segments.filter(function (segmento) { return segmento.falante; }).map(function (segmento) {
|
||||
return { speaker: segmento.falante, start: segmento.start, end: segmento.end };
|
||||
});
|
||||
if (falantes.length) renderSpeakerConfiguration(falantes, turnos);
|
||||
renderSpeakerConfiguration(falantes, turnos);
|
||||
atualizarTelaRefinar(falantes, audioDoBanco);
|
||||
status.textContent = "Carregada";
|
||||
info.textContent = silenceState.sequenceName + " · " + quantidade + " trecho(s) · " +
|
||||
(audioDoBanco.metricas_de_voz_disponiveis ? "métricas disponíveis" : "métricas não realizadas") + " · " +
|
||||
@@ -2224,6 +2250,36 @@ function silenceLoadScannerAnalysis() {
|
||||
showToast("err", info.textContent);
|
||||
}
|
||||
setBusy("btnLoadScannerAnalysis", false);
|
||||
setBusy("btnRefinarLoad", false);
|
||||
}
|
||||
|
||||
function refinarAoAbrir() {
|
||||
var info = document.getElementById("refinarInfo");
|
||||
if (!info || !silenceState.sequenceId) return;
|
||||
info.textContent = silenceState.sequenceName + " · " + (silenceState.speakers.length
|
||||
? silenceState.speakers.length + " falante(s) carregado(s)."
|
||||
: "Nenhum falante carregado.");
|
||||
}
|
||||
|
||||
function refinarCarregarAnalise() {
|
||||
silenceLoadScannerAnalysis();
|
||||
}
|
||||
|
||||
function atualizarTelaRefinar(falantes, audioDoBanco) {
|
||||
var status = document.getElementById("refinarStatus");
|
||||
var info = document.getElementById("refinarInfo");
|
||||
var vazio = document.getElementById("refinarSpeakerEmpty");
|
||||
var count = document.getElementById("refinarSpeakerCount");
|
||||
var save = document.getElementById("btnRefinarSave");
|
||||
if (!status || !info || !vazio || !count) return;
|
||||
var quantidade = Array.isArray(falantes) ? falantes.length : 0;
|
||||
status.textContent = quantidade ? "Pronto" : "Sem diarização";
|
||||
info.textContent = silenceState.sequenceName + " · " + (audioDoBanco && audioDoBanco.diarizacao_disponivel
|
||||
? quantidade + " falante(s) detectado(s)."
|
||||
: "A transcrição está disponível, mas a diarização não foi realizada.");
|
||||
count.textContent = quantidade ? quantidade + (quantidade === 1 ? " falante detectado" : " falantes detectados") : "Nenhum falante carregado";
|
||||
vazio.hidden = quantidade > 0;
|
||||
if (save) save.disabled = quantidade === 0;
|
||||
}
|
||||
|
||||
var speakerAudioPlayer = null;
|
||||
@@ -2490,6 +2546,7 @@ function silenceSelectSequence() {
|
||||
setStep(2, "ready", "Sua vez");
|
||||
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();
|
||||
@@ -2748,6 +2805,7 @@ function silenceDiarizeSpeakers(onFinished) {
|
||||
if (parsed && Array.isArray(parsed.speakers)) {
|
||||
silenceState.speakerTurns = Array.isArray(parsed.turns) ? parsed.turns : [];
|
||||
renderSpeakerConfiguration(parsed.speakers, silenceState.speakerTurns);
|
||||
atualizarTelaRefinar(parsed.speakers, { diarizacao_disponivel: true });
|
||||
}
|
||||
var msg = parsed
|
||||
? "Foram detectados " + parsed.num_speakers + (parsed.num_speakers === 1 ? " falante" : " falantes") + ": " + parsed.speakers.join(", ") + "."
|
||||
@@ -2767,20 +2825,45 @@ function speakerNameDefault(speakerId) {
|
||||
return speakerId;
|
||||
}
|
||||
|
||||
function renderSpeakerConfiguration(speakerIds, speakerTurns) {
|
||||
var config = document.getElementById("silenceSpeakerConfig");
|
||||
var list = document.getElementById("silenceSpeakerList");
|
||||
var count = document.getElementById("silenceSpeakerCount");
|
||||
function carregarNomesDeFalantes() {
|
||||
if (!silenceState.sequenceId) return {};
|
||||
try {
|
||||
var salvo = JSON.parse(localStorage.getItem("mcp_falantes_" + silenceState.sequenceId) || "{}");
|
||||
return salvo && typeof salvo === "object" ? salvo : {};
|
||||
} catch (_) { return {}; }
|
||||
}
|
||||
|
||||
function salvarNomesDeFalantes() {
|
||||
if (!silenceState.sequenceId) return;
|
||||
var salvo = {};
|
||||
silenceState.speakers.forEach(function (speaker) {
|
||||
salvo[speaker.id] = { name: speaker.name, active: speaker.active !== false };
|
||||
});
|
||||
try { localStorage.setItem("mcp_falantes_" + silenceState.sequenceId, JSON.stringify(salvo)); } catch (_) {}
|
||||
}
|
||||
|
||||
function refinarSalvarFalantes() {
|
||||
salvarNomesDeFalantes();
|
||||
showToast("ok", "Identificação dos falantes salva.");
|
||||
}
|
||||
|
||||
function renderSpeakerConfiguration(speakerIds, speakerTurns, alvo) {
|
||||
alvo = alvo || {};
|
||||
var config = document.getElementById(alvo.config || "silenceSpeakerConfig");
|
||||
var list = document.getElementById(alvo.list || "silenceSpeakerList");
|
||||
var count = document.getElementById(alvo.count || "silenceSpeakerCount");
|
||||
if (!config || !list || !count) return;
|
||||
silenceState.speakerTurns = Array.isArray(speakerTurns) ? speakerTurns : silenceState.speakerTurns;
|
||||
var previous = {};
|
||||
silenceState.speakers.forEach(function (speaker) { previous[speaker.id] = speaker; });
|
||||
var salvo = carregarNomesDeFalantes();
|
||||
silenceState.speakers = speakerIds.map(function (id) {
|
||||
var old = previous[id];
|
||||
var salvoSpeaker = salvo[id] || {};
|
||||
return {
|
||||
id: id,
|
||||
name: old && old.name ? old.name : speakerNameDefault(id),
|
||||
active: !old || old.active !== false,
|
||||
name: old && old.name ? old.name : (salvoSpeaker.name || speakerNameDefault(id)),
|
||||
active: old ? old.active !== false : salvoSpeaker.active !== false,
|
||||
};
|
||||
});
|
||||
count.textContent = silenceState.speakers.length +
|
||||
@@ -2806,13 +2889,19 @@ function renderSpeakerConfiguration(speakerIds, speakerTurns) {
|
||||
input.placeholder = "Nome do falante";
|
||||
input.maxLength = 120;
|
||||
input.setAttribute("aria-label", "Nome de " + speaker.id);
|
||||
input.addEventListener("input", function () { silenceState.speakers[index].name = input.value.trim() || speaker.id; });
|
||||
input.addEventListener("input", function () {
|
||||
silenceState.speakers[index].name = input.value.trim() || speaker.id;
|
||||
salvarNomesDeFalantes();
|
||||
});
|
||||
var activeLabel = document.createElement("label");
|
||||
activeLabel.className = "checkbox-field speaker-active-field";
|
||||
var active = document.createElement("input");
|
||||
active.type = "checkbox";
|
||||
active.checked = speaker.active;
|
||||
active.addEventListener("change", function () { silenceState.speakers[index].active = active.checked; });
|
||||
active.addEventListener("change", function () {
|
||||
silenceState.speakers[index].active = active.checked;
|
||||
salvarNomesDeFalantes();
|
||||
});
|
||||
var activeText = document.createElement("span");
|
||||
activeText.textContent = "Incluir no vídeo";
|
||||
activeLabel.appendChild(active);
|
||||
@@ -2824,6 +2913,12 @@ function renderSpeakerConfiguration(speakerIds, speakerTurns) {
|
||||
list.appendChild(row);
|
||||
});
|
||||
config.hidden = silenceState.speakers.length === 0;
|
||||
var refineConfig = document.getElementById("refinarSpeakerConfig");
|
||||
if (refineConfig && config.id !== refineConfig.id) {
|
||||
renderSpeakerConfiguration(speakerIds, speakerTurns, {
|
||||
config: "refinarSpeakerConfig", list: "refinarSpeakerList", count: "refinarSpeakerCount"
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function speakerAudioFileUrl(filePath) {
|
||||
@@ -3012,6 +3107,7 @@ function silenceGenerateJson() {
|
||||
pathCell.textContent = out;
|
||||
pathCell.title = out;
|
||||
document.getElementById("silenceJsonCard").hidden = false;
|
||||
document.getElementById("jsonFileActions").hidden = false;
|
||||
document.getElementById("silencePlanInfo").textContent =
|
||||
"Envie este arquivo a um agente de IA externo. Ele deve devolver o plano de edição em JSON para o passo 5.";
|
||||
|
||||
|
||||
@@ -417,6 +417,8 @@ textarea { padding: 8px 10px; font: 10px var(--font-mono); color: var(--text-sec
|
||||
.inline-form > * + * { margin-top: 8px; }
|
||||
.button-row { display: flex; gap: 8px; }
|
||||
.button-row .button { flex: 1; }
|
||||
.json-file-actions { margin-top: 8px; }
|
||||
.json-file-actions .button { min-width: 0; }
|
||||
|
||||
/* -------------------------- Catálogo editorial ------------------------- */
|
||||
.video-type-list { display: grid; gap: 6px; }
|
||||
@@ -479,6 +481,9 @@ textarea { padding: 8px 10px; font: 10px var(--font-mono); color: var(--text-sec
|
||||
.speaker-play-button.is-playing { color: var(--accent); }
|
||||
.speaker-name-input { width: 100%; }
|
||||
.speaker-active-field { margin: 0; padding: 6px 8px; white-space: nowrap; }
|
||||
.refinar-actions { display: flex; gap: 8px; flex-wrap: wrap; }
|
||||
.refinar-actions .button { flex: 1 1 180px; }
|
||||
#refinarSpeakerConfig .button { margin-top: 10px; }
|
||||
|
||||
/* ------------------------------- Callouts ------------------------------- */
|
||||
.callout { padding: 10px 11px; border: 1px solid var(--border); border-left-width: 3px; border-radius: 6px; background: var(--surface-deep); }
|
||||
|
||||
Reference in New Issue
Block a user