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:
João Henrique
2026-09-09 21:39:32 -04:00
co-authored by Claude Sonnet 5
parent 06e4d13e86
commit 5f3c7f6a24
3 changed files with 134 additions and 28 deletions
+110 -12
View File
@@ -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");