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:
@@ -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
@@ -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");
|
||||
|
||||
@@ -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; }
|
||||
|
||||
@@ -59,7 +59,9 @@ def executar(entrada: Path, saida: Path) -> Path:
|
||||
pedido = json.loads(entrada.read_text(encoding="utf-8"))
|
||||
configuracao = ConfiguracaoVisualDoScanner.de_dict(pedido["perfil"])
|
||||
timeline = ConversorDeTimeline().converter(pedido["timeline"])
|
||||
clipes = [clipe for faixa in timeline.faixas if faixa.tipo == "video"
|
||||
analisar_imagem = configuracao.modo_de_analise in {"imagem", "ambos"}
|
||||
analisar_som = configuracao.modo_de_analise in {"som", "ambos"}
|
||||
clipes = [clipe for faixa in timeline.faixas if analisar_imagem and faixa.tipo == "video"
|
||||
and faixa.indice in configuracao.faixas_de_video for clipe in faixa.clipes]
|
||||
total_estimado = sum(max(1, math.ceil((clipe.intervalo_na_origem or clipe.intervalo_na_timeline).duracao /
|
||||
configuracao.intervalo_em_segundos)) + 1 for clipe in clipes) or 1
|
||||
@@ -105,7 +107,8 @@ def executar(entrada: Path, saida: Path) -> Path:
|
||||
"arquivo_original": clipe.arquivo}, "erro": str(erro)})
|
||||
concluidos += peso
|
||||
faixas_de_audio = [faixa for faixa in timeline.faixas
|
||||
if faixa.tipo == "audio" and faixa.indice in configuracao.faixas_de_audio]
|
||||
if analisar_som and faixa.tipo == "audio"
|
||||
and faixa.indice in configuracao.faixas_de_audio]
|
||||
audio = [{"indice": faixa.indice, "nome": faixa.nome,
|
||||
"clipes": [{"identificador": clipe.identificador, "nome": clipe.nome,
|
||||
"inicio": clipe.intervalo_na_timeline.inicio, "fim": clipe.intervalo_na_timeline.fim}
|
||||
|
||||
@@ -38,6 +38,7 @@ class ConfiguracaoVisualDoScanner:
|
||||
resolucao_maxima: int = 1080
|
||||
faixas_de_video: tuple[int, ...] = ()
|
||||
faixas_de_audio: tuple[int, ...] = ()
|
||||
modo_de_analise: str = "ambos"
|
||||
recursos_vision: frozenset[str] = RECURSOS_VISION
|
||||
detectar_cenas: bool = True
|
||||
analisar_continuidade: bool = True
|
||||
@@ -56,6 +57,8 @@ class ConfiguracaoVisualDoScanner:
|
||||
maximo_de_frames_interpretados: int | None = None
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
if self.modo_de_analise not in {"som", "imagem", "ambos"}:
|
||||
raise ValueError("modo_de_analise deve ser som, imagem ou ambos.")
|
||||
if not 0.04 <= self.intervalo_em_segundos <= 60:
|
||||
raise ValueError("intervalo_em_segundos deve estar entre 0,04 e 60.")
|
||||
if self.resolucao_maxima != 0 and not 240 <= self.resolucao_maxima <= 8640:
|
||||
@@ -83,6 +86,7 @@ class ConfiguracaoVisualDoScanner:
|
||||
resolucao_maxima=int(dados.get("resolucao_maxima", 1080)),
|
||||
faixas_de_video=tuple(sorted(set(int(item) for item in dados.get("faixas_de_video", ())))),
|
||||
faixas_de_audio=tuple(sorted(set(int(item) for item in dados.get("faixas_de_audio", ())))),
|
||||
modo_de_analise=str(dados.get("modo_de_analise", "ambos")),
|
||||
recursos_vision=frozenset(dados.get("recursos_vision", RECURSOS_VISION)),
|
||||
detectar_cenas=bool(dados.get("detectar_cenas", True)),
|
||||
analisar_continuidade=bool(dados.get("analisar_continuidade", True)),
|
||||
@@ -103,6 +107,7 @@ class ConfiguracaoVisualDoScanner:
|
||||
"resolucao_maxima": self.resolucao_maxima,
|
||||
"faixas_de_video": list(self.faixas_de_video),
|
||||
"faixas_de_audio": list(self.faixas_de_audio),
|
||||
"modo_de_analise": self.modo_de_analise,
|
||||
"recursos_vision": sorted(self.recursos_vision),
|
||||
"detectar_cenas": self.detectar_cenas,
|
||||
"analisar_continuidade": self.analisar_continuidade,
|
||||
|
||||
Reference in New Issue
Block a user