diff --git a/code/cep-plugin/index.html b/code/cep-plugin/index.html index 70cce17..2f51e95 100755 --- a/code/cep-plugin/index.html +++ b/code/cep-plugin/index.html @@ -90,31 +90,29 @@

Análise do Scanner

Não carregada

Carregue a análise mais recente do Scanner antes de gerar o plano de edição.

+
-
+
-

Escolher o vídeo

-

Lista as timelines do projeto e lê o primeiro clipe da escolhida.

+

Selecionar o tipo de vídeo

+

Escolha o perfil cadastrado que orientará a edição e as instruções para a IA.

- Comece aqui + Aguardando análise
- - - -

Abra o projeto no Premiere e carregue as timelines.

+ + +

Os tipos disponíveis são carregados do cadastro de Tipos de vídeo.

diff --git a/code/cep-plugin/main.js b/code/cep-plugin/main.js index 7b6fd3b..4db4f4c 100755 --- a/code/cep-plugin/main.js +++ b/code/cep-plugin/main.js @@ -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 = ''; + 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"); diff --git a/code/cep-plugin/styles.css b/code/cep-plugin/styles.css index 0e1b115..ccc81b0 100755 --- a/code/cep-plugin/styles.css +++ b/code/cep-plugin/styles.css @@ -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 {