feat: substitui seleção de sequência por checklist da análise do Scanner na aba Editar vídeo
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
06e4d13e86
commit
5f3c7f6a24
+14
-16
@@ -90,31 +90,29 @@
|
||||
<section class="card scanner-analysis-card" id="scannerAnalysisCard">
|
||||
<div class="card-head"><h2>Análise do Scanner</h2><span class="step-chip" id="scannerAnalysisStatus">Não carregada</span></div>
|
||||
<p class="field-help" id="scannerAnalysisInfo">Carregue a análise mais recente do Scanner antes de gerar o plano de edição.</p>
|
||||
<ul class="scanner-analysis-checks" id="scannerAnalysisChecks" aria-label="Componentes carregados da análise">
|
||||
<li id="scannerCheckTranscription" data-state="pending"><span class="check-dot" aria-hidden="true"></span><span>Transcrição</span><em>Aguardando</em></li>
|
||||
<li id="scannerCheckDiarization" data-state="pending"><span class="check-dot" aria-hidden="true"></span><span>Detecção de falantes</span><em>Aguardando</em></li>
|
||||
<li id="scannerCheckVoiceFeatures" data-state="pending"><span class="check-dot" aria-hidden="true"></span><span>Métrica de voz</span><em>Aguardando</em></li>
|
||||
</ul>
|
||||
<button id="btnLoadScannerAnalysis" class="button button-primary" onclick="silenceLoadScannerAnalysis()" type="button">Carregar análise do Scanner</button>
|
||||
</section>
|
||||
|
||||
<section class="step editar-video-legado" id="step1" data-state="ready">
|
||||
<section class="step" id="step1" data-state="locked">
|
||||
<div class="step-head">
|
||||
<span class="step-number" aria-hidden="true">1</span>
|
||||
<div class="step-title">
|
||||
<h2>Escolher o vídeo</h2>
|
||||
<p>Lista as timelines do projeto e lê o primeiro clipe da escolhida.</p>
|
||||
<h2>Selecionar o tipo de vídeo</h2>
|
||||
<p>Escolha o perfil cadastrado que orientará a edição e as instruções para a IA.</p>
|
||||
</div>
|
||||
<span class="step-chip" id="step1Chip">Comece aqui</span>
|
||||
<span class="step-chip" id="step1Chip">Aguardando análise</span>
|
||||
</div>
|
||||
<div class="step-body">
|
||||
<button id="btnDetectClip" class="button button-primary" onclick="silenceListSequences()" type="button">Carregar sequências</button>
|
||||
<div class="sequence-picker" id="silenceSequencePicker" hidden>
|
||||
<label class="field-label" for="silenceSequenceSelect">Escolha a timeline</label>
|
||||
<select id="silenceSequenceSelect" class="select-field" onchange="silenceSelectSequence()"></select>
|
||||
<p class="field-help" id="silenceSequenceListInfo"></p>
|
||||
</div>
|
||||
<dl class="kv" id="silenceClipCard" hidden>
|
||||
<div><dt>Sequência</dt><dd id="clipSequenceName">—</dd></div>
|
||||
<div><dt>Arquivo</dt><dd id="clipFileName">—</dd></div>
|
||||
<div><dt>Caminho</dt><dd class="kv-path" id="clipMediaPath">—</dd></div>
|
||||
</dl>
|
||||
<p class="field-help" id="silenceClipInfo">Abra o projeto no Premiere e carregue as timelines.</p>
|
||||
<label class="field-label" for="silenceTipoVideoSelect">Tipo de vídeo</label>
|
||||
<select id="silenceTipoVideoSelect" class="select-field" onchange="silenceSelecionarTipoVideo()" disabled>
|
||||
<option value="">Selecione um tipo cadastrado</option>
|
||||
</select>
|
||||
<p class="field-help" id="silenceTipoVideoInfo">Os tipos disponíveis são carregados do cadastro de Tipos de vídeo.</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
+110
-12
@@ -265,7 +265,7 @@ function switchTab(name) {
|
||||
btn.setAttribute("aria-selected", String(active));
|
||||
});
|
||||
if (name === "models") renderModelList();
|
||||
if (name === "silence") { syncTranscribeModelSelect(); syncLanguageInfo(); }
|
||||
if (name === "silence") { syncTranscribeModelSelect(); syncLanguageInfo(); renderizarTiposDeVideoParaEdicao(); }
|
||||
if (name === "settings") renderEditorPersonalities();
|
||||
if (name === "scanner") { renderScannerTranscriptionOptions(); loadScannerProfile(); renderScannerHuggingFaceStatus(); scannerListarAnalisesExistentes(); }
|
||||
if (name === "refinar") refinarAoAbrir();
|
||||
@@ -2112,10 +2112,71 @@ var silenceState = {
|
||||
transcribeModel: null,
|
||||
cachedTranscriptPath: null,
|
||||
jsonOutputPath: null,
|
||||
tipoVideoId: null,
|
||||
tipoVideo: null,
|
||||
speakers: [],
|
||||
speakerTurns: [],
|
||||
};
|
||||
|
||||
function atualizarCheckDaAnalise(id, disponivel, textoDisponibilidade) {
|
||||
var item = document.getElementById(id);
|
||||
if (!item) return;
|
||||
item.setAttribute("data-state", disponivel ? "done" : "missing");
|
||||
var estado = item.querySelector("em");
|
||||
if (estado) estado.textContent = textoDisponibilidade || (disponivel ? "Carregado" : "Não disponível");
|
||||
}
|
||||
|
||||
function renderizarTiposDeVideoParaEdicao() {
|
||||
var seletor = document.getElementById("silenceTipoVideoSelect");
|
||||
if (!seletor) return;
|
||||
var selecionado = silenceState.tipoVideoId || "";
|
||||
seletor.innerHTML = '<option value="">Selecione um tipo cadastrado</option>';
|
||||
tiposVideoLer().filter(function (tipo) { return tipo.ativo !== false; }).forEach(function (tipo) {
|
||||
var opcao = document.createElement("option");
|
||||
opcao.value = tipo.id;
|
||||
opcao.textContent = tipo.nome;
|
||||
opcao.selected = tipo.id === selecionado;
|
||||
seletor.appendChild(opcao);
|
||||
});
|
||||
seletor.disabled = !silenceState.sequenceId;
|
||||
}
|
||||
|
||||
function silenceSelecionarTipoVideo() {
|
||||
var seletor = document.getElementById("silenceTipoVideoSelect");
|
||||
var tipo = tiposVideoLer().filter(function (item) { return item.id === seletor.value; })[0] || null;
|
||||
silenceState.tipoVideoId = tipo ? tipo.id : null;
|
||||
silenceState.tipoVideo = tipo;
|
||||
if (!tipo) {
|
||||
document.getElementById("silenceTipoVideoInfo").textContent = "Selecione um tipo cadastrado para continuar.";
|
||||
setStep(1, "ready", "Selecione um tipo");
|
||||
document.getElementById("btnBuildPlan").disabled = true;
|
||||
return;
|
||||
}
|
||||
document.getElementById("silenceTipoVideoInfo").textContent = tipo.nome + " · instruções serão incluídas no JSON para a IA.";
|
||||
setStep(1, "done", "Selecionado");
|
||||
document.getElementById("btnBuildPlan").disabled = false;
|
||||
silenceLog("Tipo de vídeo selecionado: " + tipo.nome + ".", "ok");
|
||||
}
|
||||
|
||||
function tipoVideoResumoParaJson(tipo) {
|
||||
if (!tipo) return null;
|
||||
return {
|
||||
id: tipo.id,
|
||||
nome: tipo.nome,
|
||||
categoria: tipo.categoria,
|
||||
objetivo: { principal: tipo.objetivo, secundario: tipo.objetivo_secundario, publico: tipo.publico, mensagem: tipo.mensagem },
|
||||
narrativa: { tipo: tipo.tipo_narrativa, foco: tipo.foco_narrativo, storytelling: tipo.storytelling, estrutura: tipo.estrutura },
|
||||
emocao: { objetivo: tipo.emocao, intensidade: tipo.intensidade, tom: tipo.tom, construcao: tipo.construcao_emocao },
|
||||
formato: { canais: tipo.canais, proporcao: tipo.proporcao, duracao_minima_segundos: tipo.duracao_minima, duracao_maxima_segundos: tipo.duracao_maxima_segundos, ritmo: tipo.ritmo },
|
||||
prioridades: tipo.prioridades,
|
||||
elementos_de_edicao: { broll: tipo.usa_broll, legendas: tipo.usa_legendas, textos: tipo.usa_textos, graficos: tipo.usa_graficos, zoom: tipo.usa_zoom, cortes: tipo.cortes, orientacao_de_broll: tipo.broll, textos_e_legendas: tipo.texto },
|
||||
audio: { musica: tipo.usa_musica, intensidade_da_musica: tipo.intensidade_musica, prioridade_da_voz: tipo.prioridade_voz, tratamento: tipo.som },
|
||||
regras_de_edicao: tipo.diretriz,
|
||||
restricoes: tipo.restricoes,
|
||||
criterios_de_qualidade: tipo.qualidade
|
||||
};
|
||||
}
|
||||
|
||||
function silenceLocalizarUltimoRelatorioDoScanner() {
|
||||
var raiz = scannerOutputRoot();
|
||||
var candidatos = [];
|
||||
@@ -2188,6 +2249,20 @@ function silenceLocalizarAudioParaFalantes(pasta) {
|
||||
return encontrados[0] || null;
|
||||
}
|
||||
|
||||
function silenceLocalizarMidiaNoRelatorio(caminhoDoRelatorio, dadosDoRelatorio) {
|
||||
var arquivosDeAudio = dadosDoRelatorio.artefatos && dadosDoRelatorio.artefatos.audio || [];
|
||||
for (var indice = 0; indice < arquivosDeAudio.length; indice++) {
|
||||
try {
|
||||
var dadosDoAudio = JSON.parse(fs.readFileSync(path.join(path.dirname(caminhoDoRelatorio), arquivosDeAudio[indice]), "utf-8"));
|
||||
var clipes = dadosDoAudio.faixa && dadosDoAudio.faixa.clipes || [];
|
||||
for (var indiceDoClipe = 0; indiceDoClipe < clipes.length; indiceDoClipe++) {
|
||||
if (clipes[indiceDoClipe].sourceFile) return clipes[indiceDoClipe].sourceFile;
|
||||
}
|
||||
} catch (_) {}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function silenceLoadScannerAnalysis() {
|
||||
var status = document.getElementById("scannerAnalysisStatus");
|
||||
var info = document.getElementById("scannerAnalysisInfo");
|
||||
@@ -2207,12 +2282,14 @@ function silenceLoadScannerAnalysis() {
|
||||
var sequencia = dadosDoRelatorio.sequencia || {};
|
||||
silenceState.sequenceId = sequencia.id || null;
|
||||
silenceState.sequenceName = sequencia.nome || path.basename(caminhoDoRelatorio);
|
||||
silenceState.mediaPath = null;
|
||||
silenceState.mediaPath = silenceLocalizarMidiaNoRelatorio(caminhoDoRelatorio, dadosDoRelatorio);
|
||||
silenceState.fileBaseName = path.basename(path.dirname(caminhoDoRelatorio));
|
||||
silenceState.outputDir = path.dirname(caminhoDoRelatorio);
|
||||
silenceState.audioCachePath = silenceLocalizarAudioParaFalantes(silenceState.outputDir);
|
||||
silenceState.transcriptPath = path.join(silenceState.outputDir, "transcricao-do-scanner.json");
|
||||
silenceState.cachedTranscriptPath = null;
|
||||
silenceState.tipoVideoId = null;
|
||||
silenceState.tipoVideo = null;
|
||||
fs.writeFileSync(silenceState.transcriptPath, JSON.stringify(
|
||||
silenceConverterRelatorioDoScannerParaTranscricao(caminhoDoRelatorio, dadosDoRelatorio), null, 2), "utf-8");
|
||||
var transcricao = JSON.parse(fs.readFileSync(silenceState.transcriptPath, "utf-8"));
|
||||
@@ -2221,6 +2298,12 @@ function silenceLoadScannerAnalysis() {
|
||||
if (!statusDoBanco || !statusDoBanco.audio) throw new Error("A análise não foi encontrada no banco de dados.");
|
||||
var audioDoBanco = statusDoBanco.audio;
|
||||
var falantes = audioDoBanco.falantes || [];
|
||||
var temTranscricao = quantidade > 0;
|
||||
var temDiarizacao = !!audioDoBanco.diarizacao_disponivel || falantes.length > 0;
|
||||
var temMetricasDeVoz = !!audioDoBanco.metricas_de_voz_disponiveis;
|
||||
atualizarCheckDaAnalise("scannerCheckTranscription", temTranscricao, temTranscricao ? "Carregado" : "Não encontrada");
|
||||
atualizarCheckDaAnalise("scannerCheckDiarization", temDiarizacao, temDiarizacao ? "Carregado" : "Não realizada");
|
||||
atualizarCheckDaAnalise("scannerCheckVoiceFeatures", temMetricasDeVoz, temMetricasDeVoz ? "Carregado" : "Não realizada");
|
||||
var turnos = transcricao.segments.filter(function (segmento) { return segmento.falante; }).map(function (segmento) {
|
||||
return { speaker: segmento.falante, start: segmento.start, end: segmento.end };
|
||||
});
|
||||
@@ -2231,17 +2314,19 @@ function silenceLoadScannerAnalysis() {
|
||||
(audioDoBanco.metricas_de_voz_disponiveis ? "métricas disponíveis" : "métricas não realizadas") + " · " +
|
||||
(audioDoBanco.diarizacao_disponivel ? falantes.length + " falante(s)" : "diarização não realizada");
|
||||
setStep(3, "ready", "Pronto");
|
||||
setStep(4, "ready", "Pronto");
|
||||
setStep(1, "ready", "Selecione um tipo");
|
||||
setStep(4, "locked", "Selecione o tipo");
|
||||
setStep(5, "locked", "Bloqueado");
|
||||
document.getElementById("btnBuildPlan").disabled = false;
|
||||
document.getElementById("btnDiarize").disabled = true;
|
||||
document.getElementById("btnVoiceFeatures").disabled = true;
|
||||
document.getElementById("silenceDiarizeInfo").textContent = audioDoBanco.diarizacao_disponivel
|
||||
renderizarTiposDeVideoParaEdicao();
|
||||
document.getElementById("btnBuildPlan").disabled = true;
|
||||
document.getElementById("btnDiarize").disabled = false;
|
||||
document.getElementById("btnVoiceFeatures").disabled = false;
|
||||
document.getElementById("silenceDiarizeInfo").textContent = temDiarizacao
|
||||
? falantes.length + " falante(s) disponíveis no banco de dados."
|
||||
: "Diarização não realizada nesta análise do Scanner.";
|
||||
document.getElementById("silenceVoiceFeaturesInfo").textContent = audioDoBanco.metricas_de_voz_disponiveis
|
||||
? audioDoBanco.falas_com_metricas + " trecho(s) com métricas de voz no banco."
|
||||
: "Métricas de voz não realizadas nesta análise do Scanner.";
|
||||
: "Diarização não realizada nesta análise do Scanner. Use Refazer se necessário.";
|
||||
document.getElementById("silenceVoiceFeaturesInfo").textContent = temMetricasDeVoz
|
||||
? (audioDoBanco.falas_com_metricas || 0) + " trecho(s) com métricas de voz no banco."
|
||||
: "Métricas de voz não realizadas nesta análise do Scanner. Use Refazer se necessário.";
|
||||
silenceLog("Análise do Scanner carregada: " + quantidade + " trecho(s).", "ok");
|
||||
showToast("ok", "Análise do Scanner carregada.");
|
||||
} catch (erro) {
|
||||
@@ -2812,6 +2897,7 @@ function silenceDiarizeSpeakers(onFinished) {
|
||||
: "Falantes detectados.";
|
||||
silenceLog(msg, "ok");
|
||||
info.textContent = msg;
|
||||
atualizarCheckDaAnalise("scannerCheckDiarization", true, "Carregado");
|
||||
setStep(3, "done", "Enriquecido");
|
||||
taskEnd(true, msg);
|
||||
syncTranscriptToCache();
|
||||
@@ -3065,6 +3151,7 @@ function silenceComputeVoiceFeatures() {
|
||||
var msg = "Pitch, energia, velocidade e pausas calculados por trecho.";
|
||||
silenceLog(msg, "ok");
|
||||
info.textContent = msg;
|
||||
atualizarCheckDaAnalise("scannerCheckVoiceFeatures", true, "Carregado");
|
||||
setStep(3, "done", "Enriquecido");
|
||||
taskEnd(true, msg);
|
||||
syncTranscriptToCache();
|
||||
@@ -3077,6 +3164,11 @@ function silenceComputeVoiceFeatures() {
|
||||
// externo analisar. Quem decide os cortes é a IA, não este painel.
|
||||
function silenceGenerateJson() {
|
||||
if (!silenceState.transcriptPath) return;
|
||||
if (!silenceState.tipoVideo) {
|
||||
showToast("err", "Selecione o tipo de vídeo antes de gerar o JSON.");
|
||||
setStep(1, "ready", "Selecione um tipo");
|
||||
return;
|
||||
}
|
||||
setBusy("btnBuildPlan", true);
|
||||
setStep(4, "running", "Gerando…");
|
||||
silenceLog("Gerando arquivo JSON para a IA…");
|
||||
@@ -3095,6 +3187,11 @@ function silenceGenerateJson() {
|
||||
legendas: carregarConfiguracaoDeLegendas(),
|
||||
});
|
||||
transcript.speaker_configuration = speakerConfigurationForJson();
|
||||
transcript.tipo_de_video = {
|
||||
id: silenceState.tipoVideo.id,
|
||||
nome: silenceState.tipoVideo.nome,
|
||||
instrucoes: tipoVideoResumoParaJson(silenceState.tipoVideo),
|
||||
};
|
||||
|
||||
ensureDir(silenceState.outputDir);
|
||||
var out = path.join(silenceState.outputDir, "dados-para-ia.json");
|
||||
@@ -3878,11 +3975,12 @@ function downloadModel(model, btn, approxMB) {
|
||||
syncLanguageInfo();
|
||||
renderEditorPersonalities();
|
||||
restaurarConfiguracaoDeLegendas();
|
||||
renderizarTiposDeVideoParaEdicao();
|
||||
|
||||
// A aba Editar vídeo começa com a análise já produzida pelo Scanner.
|
||||
setStep(1, "locked", "Scanner");
|
||||
setStep(2, "locked", "Scanner");
|
||||
setStep(3, "ready", "Aguardando análise");
|
||||
setStep(3, "locked", "Aguardando análise");
|
||||
setStep(4, "locked", "Aguardando análise");
|
||||
setStep(5, "locked", "Aguardando plano");
|
||||
|
||||
|
||||
@@ -179,6 +179,16 @@ h1, h2, h3 { margin: 0; }
|
||||
.editorial-plan-json { width: 100%; min-height: 150px; resize: vertical; box-sizing: border-box; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 11px; line-height: 1.45; color: var(--text); background: var(--surface); border: 1px solid var(--border); border-radius: 6px; padding: 9px; }
|
||||
.editar-video-legado { display: none; }
|
||||
.scanner-analysis-card { margin-bottom: 12px; }
|
||||
.scanner-analysis-checks { display: grid; gap: 6px; margin: 10px 0 12px; padding: 0; list-style: none; }
|
||||
.scanner-analysis-checks li { display: flex; align-items: center; gap: 8px; padding: 7px 9px; border: 1px solid var(--border); border-radius: 6px; background: var(--surface-deep); color: var(--text-secondary); }
|
||||
.scanner-analysis-checks li em { margin-left: auto; color: var(--text-muted); font-size: 9px; font-style: normal; }
|
||||
.scanner-analysis-checks li[data-state="done"] { border-color: rgba(112, 217, 135, .3); color: var(--text); }
|
||||
.scanner-analysis-checks li[data-state="done"] .check-dot { display: inline-flex; align-items: center; justify-content: center; width: 14px; height: 14px; background: var(--green); box-shadow: 0 0 0 3px var(--green-soft); color: #10240f; font-size: 10px; font-weight: 800; }
|
||||
.scanner-analysis-checks li[data-state="done"] .check-dot::after { content: "✓"; }
|
||||
.scanner-analysis-checks li[data-state="missing"] { border-color: rgba(233, 184, 93, .3); }
|
||||
.scanner-analysis-checks li[data-state="missing"] .check-dot { background: var(--amber); }
|
||||
.scanner-analysis-checks li[data-state="done"] em { color: var(--green); }
|
||||
.scanner-analysis-checks li[data-state="missing"] em { color: var(--amber); }
|
||||
|
||||
/* --------------------- Painel de progresso (task dock) ------------------- */
|
||||
.task-dock {
|
||||
|
||||
Reference in New Issue
Block a user