feat: criado modo explícito de análise do Scanner para separar som

- criado modo explícito de análise do Scanner para separar som, imagem ou ambos

Resumo:
- 5 arquivos alterados
- 0 novos
- 5 modificados
- 0 removidos

 5 files changed, 68 insertions(+), 11 deletions(-)

Arquivos:
  - code/cep-plugin/index.html
  - code/cep-plugin/main.js
  - code/cep-plugin/styles.css
  - code/engine/executar_scanner.py
  - code/engine/scanner/configuracao_visual.py
This commit is contained in:
João Henrique
2026-09-09 17:04:49 -04:00
parent 53a8307f38
commit 89288d95f0
5 changed files with 68 additions and 11 deletions
+17 -5
View File
@@ -260,8 +260,22 @@
<!-- ============================== SCANNER =============================== -->
<div class="tab-panel" id="tabScanner" role="tabpanel" aria-labelledby="tabBtnScanner" hidden>
<p class="tab-intro">Escolha o que será lido no arquivo original. O Scanner usa os tempos da timeline, mas não exporta frames pelo Premiere.</p>
<section class="card scanner-mode-card">
<div class="card-head"><h2>Tipo de análise</h2><span class="card-kicker">Primeiro escolha o fluxo</span></div>
<div class="scanner-mode-options" role="radiogroup" aria-label="Tipo de análise do Scanner">
<label class="scanner-mode-option"><input type="radio" name="scannerMode" value="som" checked onchange="scannerAtualizarModo()"><span><strong>Somente áudio</strong><small>Transcrição e fala com Whisper</small></span></label>
<label class="scanner-mode-option"><input type="radio" name="scannerMode" value="imagem" onchange="scannerAtualizarModo()"><span><strong>Somente imagem</strong><small>Frames, cenas e evidências visuais</small></span></label>
<label class="scanner-mode-option"><input type="radio" name="scannerMode" value="ambos" onchange="scannerAtualizarModo()"><span><strong>Áudio + imagem</strong><small>Executa os dois fluxos</small></span></label>
</div>
<p class="field-help" id="scannerModeHelp">O Scanner vai ignorar completamente a análise de frames e processar apenas as faixas de áudio.</p>
</section>
<section class="card scanner-track-discovery">
<div class="card-head"><h2>Timeline e faixas</h2></div>
<button class="button button-ghost" id="btnScannerDetectTracks" onclick="scannerDetectTracks()" type="button">Detectar faixas da sequência</button>
<p class="field-help" id="scannerTracksHelp">Abra a sequência desejada e detecte as faixas para selecioná-las.</p>
</section>
<div class="scanner-media-grid">
<section class="card scanner-media-card">
<section class="card scanner-media-card" id="scannerImageOptions">
<div class="card-head"><h2>Imagem</h2><span class="card-kicker">Vídeo e cenas</span></div>
<p class="field-help">Escolha as faixas e os recursos visuais que o Scanner deve analisar.</p>
<label class="field-label" for="scannerInterval">Segundos por quadro</label>
@@ -277,8 +291,6 @@
<option value="720">720p — mais rápido</option>
</select>
<p class="field-help">Reduzir a resolução acelera a extração e a leitura Vision sem perder precisão no rosto, pose ou objetos.</p>
<button class="button button-ghost" id="btnScannerDetectTracks" onclick="scannerDetectTracks()" type="button">Detectar faixas da sequência</button>
<p class="field-help" id="scannerTracksHelp">Abra a sequência desejada e detecte as faixas para selecioná-las.</p>
<div class="scanner-track-list"><span class="field-label">Faixas de vídeo</span><div id="scannerVideoTracks"></div></div>
<div class="scanner-subsection"><div class="card-head"><h3>O que detectar</h3></div>
<label class="checkbox-field"><input type="checkbox" data-scanner-resource="faces" checked><span>Rostos e qualidade facial</span></label>
@@ -296,7 +308,7 @@
<label class="checkbox-field"><input id="scannerInterpret" type="checkbox" checked><span>Descrever a cena com Foundation Models</span></label>
</div>
</section>
<section class="card scanner-media-card">
<section class="card scanner-media-card" id="scannerAudioOptions">
<div class="card-head"><h2>Som</h2><span class="card-kicker">Áudio e fala</span></div>
<p class="field-help">Escolha as faixas de áudio e configure a transcrição local do Whisper.</p>
<div class="scanner-track-list"><span class="field-label">Faixas de áudio</span><div id="scannerAudioTracks"></div></div>
@@ -309,7 +321,7 @@
</section>
</div>
<section class="card scanner-execution-card">
<div class="button-row"><button class="button button-ghost" onclick="saveScannerProfile()" type="button">Salvar perfil</button><button class="button button-primary" id="btnScannerProcess" onclick="scannerProcess()" type="button" disabled>Processar</button></div>
<div class="button-row"><button class="button button-ghost" onclick="saveScannerProfile()" type="button">Salvar perfil</button><button class="button button-primary" id="btnScannerProcess" onclick="scannerProcess()" type="button" disabled>Processar áudio</button></div>
<p class="field-help" id="scannerProfileStatus">Perfil padrão pronto.</p>
<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>
+31 -3
View File
@@ -1119,6 +1119,7 @@ function scannerOutputRoot() { return path.join(os.homedir(), ".premiere-mcp", "
function scannerSelectedTracks(kind) { return Array.prototype.slice.call(document.querySelectorAll("[data-scanner-track='" + kind + "']:checked")).map(function (item) { return Number(item.value); }).sort(function (a, b) { return a - b; }); }
function scannerRenderTracks(kind, tracks, selected) {
if (kind === "audio" && !selected.length) selected = tracks.map(function (track) { return track.index; });
var target = document.getElementById(kind === "video" ? "scannerVideoTracks" : "scannerAudioTracks");
target.innerHTML = "";
if (!tracks.length) { target.textContent = "Nenhuma faixa encontrada."; return; }
@@ -1131,13 +1132,34 @@ function scannerUpdateIntervalLabel() {
document.getElementById("scannerIntervalValue").textContent = texto;
}
function scannerModoAtual() {
var selecionado = document.querySelector('input[name="scannerMode"]:checked');
return selecionado ? selecionado.value : "som";
}
function scannerAtualizarModo() {
var modo = scannerModoAtual();
var imagem = document.getElementById("scannerImageOptions");
var som = document.getElementById("scannerAudioOptions");
var ajuda = document.getElementById("scannerModeHelp");
var botao = document.getElementById("btnScannerProcess");
if (imagem) imagem.hidden = modo === "som";
if (som) som.hidden = modo === "imagem";
if (ajuda) ajuda.textContent = modo === "som"
? "O Scanner vai ignorar completamente a análise de frames e processar apenas as faixas de áudio."
: modo === "imagem"
? "O Scanner vai ignorar a transcrição e processar apenas frames, cenas e evidências visuais."
: "O Scanner vai executar os dois fluxos e manter os resultados separados no relatório.";
if (botao) botao.textContent = modo === "som" ? "Processar áudio" : modo === "imagem" ? "Analisar imagem" : "Processar áudio + imagem";
}
function scannerProfileFromScreen() {
var enabled = Array.prototype.slice.call(document.querySelectorAll("[data-scanner-resource]:checked")).map(function (item) { return item.getAttribute("data-scanner-resource"); });
function include(source, extras) { if (enabled.indexOf(source) >= 0) extras.forEach(function (item) { if (enabled.indexOf(item) < 0) enabled.push(item); }); }
include("faces", ["qualidade_facial"]); include("pose", ["maos"]); include("ocr", ["codigos"]); include("estetica", ["horizonte", "retangulos", "contornos"]);
var modelo = document.getElementById("scannerTranscribeModel").value;
var opcao = document.getElementById("scannerTranscribeModel").selectedOptions[0];
return { versao: 1, intervalo_em_segundos: Number(document.getElementById("scannerInterval").value), resolucao_maxima: Number(document.getElementById("scannerResolution").value), faixas_de_video: scannerSelectedTracks("video"), faixas_de_audio: scannerSelectedTracks("audio"), recursos_vision: SCANNER_ALL_RESOURCES.filter(function (item) { return enabled.indexOf(item) >= 0; }), detectar_cenas: document.getElementById("scannerScenes").checked, analisar_continuidade: document.getElementById("scannerContinuity").checked, preparar_reenquadramento: document.getElementById("scannerReframe").checked, interpretar_cena: document.getElementById("scannerInterpret").checked, transcricao: { modelo: modelo, caminho_modelo: opcao && opcao.dataset.path || "", idioma: document.getElementById("scannerTranscribeLanguage").value, diarizacao: document.getElementById("scannerDiarization").checked, usar_hugging_face: !!loadHfToken() } };
return { versao: 1, modo_de_analise: scannerModoAtual(), intervalo_em_segundos: Number(document.getElementById("scannerInterval").value), resolucao_maxima: Number(document.getElementById("scannerResolution").value), faixas_de_video: scannerSelectedTracks("video"), faixas_de_audio: scannerSelectedTracks("audio"), recursos_vision: SCANNER_ALL_RESOURCES.filter(function (item) { return enabled.indexOf(item) >= 0; }), detectar_cenas: document.getElementById("scannerScenes").checked, analisar_continuidade: document.getElementById("scannerContinuity").checked, preparar_reenquadramento: document.getElementById("scannerReframe").checked, interpretar_cena: document.getElementById("scannerInterpret").checked, transcricao: { modelo: modelo, caminho_modelo: opcao && opcao.dataset.path || "", idioma: document.getElementById("scannerTranscribeLanguage").value, diarizacao: document.getElementById("scannerDiarization").checked, usar_hugging_face: !!loadHfToken() } };
}
function saveScannerProfile() {
@@ -1146,7 +1168,10 @@ function saveScannerProfile() {
function loadScannerProfile() {
var saved; try { saved = JSON.parse(localStorage.getItem(SCANNER_PROFILE_KEY) || "null"); } catch (_) { saved = null; }
if (!saved) { scannerUpdateIntervalLabel(); return; }
if (!saved) { scannerUpdateIntervalLabel(); scannerAtualizarModo(); return; }
var modo = saved.modo_de_analise || "som";
var seletorDeModo = document.querySelector('input[name="scannerMode"][value="' + modo + '"]');
if (seletorDeModo) seletorDeModo.checked = true;
document.getElementById("scannerInterval").value = String(saved.intervalo_em_segundos || 1);
scannerUpdateIntervalLabel();
document.getElementById("scannerResolution").value = String(saved.resolucao_maxima != null ? saved.resolucao_maxima : 1080);
@@ -1155,6 +1180,7 @@ function loadScannerProfile() {
Array.prototype.slice.call(document.querySelectorAll("[data-scanner-resource]")).forEach(function (item) { item.checked = resources.indexOf(item.getAttribute("data-scanner-resource")) >= 0; });
document.getElementById("scannerScenes").checked = saved.detectar_cenas !== false; document.getElementById("scannerContinuity").checked = saved.analisar_continuidade !== false; document.getElementById("scannerReframe").checked = !!saved.preparar_reenquadramento; document.getElementById("scannerInterpret").checked = saved.interpretar_cena !== false;
if (saved.transcricao) { document.getElementById("scannerTranscribeModel").value = saved.transcricao.modelo || ""; document.getElementById("scannerTranscribeLanguage").value = saved.transcricao.idioma || loadLanguage(); document.getElementById("scannerDiarization").checked = !!saved.transcricao.diarizacao && !!loadHfToken(); }
scannerAtualizarModo();
}
function renderScannerTranscriptionOptions() {
@@ -1194,7 +1220,9 @@ function scannerDetectTracks() {
function scannerProcess() {
if (!scannerTimeline) { showToast("err", "Primeiro detecte as faixas da sequência."); return; }
var profile; try { profile=scannerProfileFromScreen(); } catch (error) { showToast("err", error.message); return; }
if (!profile.faixas_de_video.length && !profile.faixas_de_audio.length) { showToast("err", "Selecione ao menos uma faixa."); return; }
if (profile.modo_de_analise === "som" && !profile.faixas_de_audio.length) { showToast("err", "Selecione ao menos uma faixa de áudio."); return; }
if (profile.modo_de_analise === "imagem" && !profile.faixas_de_video.length) { showToast("err", "Selecione ao menos uma faixa de vídeo."); return; }
if (profile.modo_de_analise === "ambos" && (!profile.faixas_de_video.length || !profile.faixas_de_audio.length)) { showToast("err", "Selecione ao menos uma faixa de vídeo e uma de áudio."); return; }
saveScannerProfile(); var outputRoot=scannerOutputRoot(); ensureDir(outputRoot);
var stamp=new Date().toISOString().replace(/[:.]/g,"-");
var input=path.join(outputRoot,"pedido-"+stamp+".json");
+10 -1
View File
@@ -156,6 +156,15 @@ h1, h2, h3 { margin: 0; }
.scanner-track-list .checkbox-field { margin: 2px 0; padding: 5px; border: 0; background: none; }
.scanner-options-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px; align-items: start; }
.scanner-options-grid .card { margin: 0 0 10px; }
.scanner-mode-options { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 8px; }
.scanner-mode-option { display: flex; gap: 8px; align-items: flex-start; min-height: 54px; padding: 10px; border: 1px solid var(--border); border-radius: 7px; background: var(--surface-deep); cursor: pointer; }
.scanner-mode-option:has(input:checked) { border-color: var(--accent); background: rgba(124, 77, 255, .12); }
.scanner-mode-option input { margin-top: 2px; accent-color: var(--accent); }
.scanner-mode-option strong, .scanner-mode-option small { display: block; }
.scanner-mode-option strong { color: var(--text); font-size: 11px; }
.scanner-mode-option small { margin-top: 4px; color: var(--text-muted); font-size: 10px; line-height: 1.3; }
.scanner-track-discovery { margin-top: 10px; }
.scanner-media-card[hidden] { display: none; }
.scanner-media-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px; align-items: start; }
.scanner-media-card { margin: 0 0 10px; }
.scanner-media-card h3 { margin: 0; font-size: 12px; }
@@ -657,7 +666,7 @@ button:focus-visible, input:focus-visible, select:focus-visible, textarea:focus-
.tab-button { font-size: 0; gap: 0; padding: 10px 2px; }
.tab-icon { font-size: 14px; }
.field-grid { grid-template-columns: minmax(0, 1fr); }
.scanner-track-grid, .scanner-options-grid, .scanner-media-grid { grid-template-columns: minmax(0, 1fr); }
.scanner-track-grid, .scanner-options-grid, .scanner-media-grid, .scanner-mode-options { grid-template-columns: minmax(0, 1fr); }
.status-panel { grid-template-columns: 38px minmax(0, 1fr); }
.command-stat { grid-column: 2; padding: 8px 0 0; text-align: left; }
.command-stat strong, .command-stat span { display: inline; }