- aprimorada a edição por voz com validação de frases, margens seguras e backup único antes da timeline. - adicionada consulta expansível dos detalhes de cada trilha diretamente do banco Resumo: - 23 arquivos alterados - 5 novos - 18 modificados - 0 removidos 18 files changed, 321 insertions(+), 216 deletions(-) Arquivos: - .gitignore - .jhonny/analises.db - code/cep-plugin/index.html - code/cep-plugin/main.js - code/cep-plugin/styles.css - code/engine/analisar_trilhas.py - code/engine/aplicar_plano_de_edicao.py - code/engine/editor/__init__.py - code/engine/editor/backup_de_sequencia.py - code/engine/persistencia/__init__.py - code/engine/testes/test_aplicar_plano_de_edicao.py - code/plugins/premiere-pro/skills/edit-video-by-voice/SKILL.md - code/plugins/premiere-pro/skills/editar-por-voz/SKILL.md - code/plugins/premiere-pro/skills/editar-por-voz/criterios/05-zoom.md - code/plugins/premiere-pro/skills/editar-por-voz/criterios/06-texto-corte-marcador.md - code/plugins/premiere-pro/skills/editar-por-voz/criterios/08-formato-de-saida.md - code/plugins/premiere-pro/skills/editar-por-voz/criterios/10-revisao-humana.md - code/plugins/premiere-pro/skills/transcript-to-edit-actions/SKILL.md - code/engine/consultar_detalhes_de_trilha.py - code/engine/editor/validacao_semantica.py - code/engine/persistencia/leitura_semantica_do_plano.py - code/engine/testes/test_leitura_semantica_do_plano.py - code/engine/testes/test_validador_semantico_de_plano.py
738 lines
57 KiB
HTML
Executable File
738 lines
57 KiB
HTML
Executable File
<!DOCTYPE html>
|
|
<html lang="pt-BR">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
<title>MCP for Adobe Premiere Pro</title>
|
|
<link rel="stylesheet" href="styles.css">
|
|
</head>
|
|
<body>
|
|
<div class="panel-shell">
|
|
|
|
<header class="panel-header">
|
|
<div class="brand-mark" aria-hidden="true">
|
|
<svg viewBox="0 0 24 24" role="img" aria-label="Lâmpada mágica">
|
|
<path d="M8.1 14.2c-1.1-1-1.8-2.4-1.8-4.1 0-3.1 2.5-5.6 5.7-5.6s5.7 2.5 5.7 5.6c0 1.7-.7 3.1-1.8 4.1-.8.8-1.2 1.4-1.3 2.3H9.4c-.1-.9-.5-1.5-1.3-2.3Z" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linejoin="round"/>
|
|
<path d="M9.6 19h4.8M10.5 21h3M4.2 5.8l-1.3-1.3M19.8 5.8l1.3-1.3M12 2.5V1M4.2 10.1H2.5M21.5 10.1h-1.7" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"/>
|
|
<path d="m12 7.1.6 1.7 1.7.6-1.7.6-.6 1.7-.6-1.7-1.7-.6 1.7-.6.6-1.7Z" fill="currentColor" stroke="none"/>
|
|
</svg>
|
|
</div>
|
|
<div class="brand-copy">
|
|
<h1>Jhonny Marrone</h1>
|
|
<p>Conexão local com o Premiere</p>
|
|
</div>
|
|
<div class="header-status" id="headerStatus" data-state="waiting" title="Status da conexão">
|
|
<span class="header-status-dot" aria-hidden="true"></span>
|
|
<span id="headerStatusText">Iniciando…</span>
|
|
</div>
|
|
</header>
|
|
|
|
<nav class="tab-bar" role="tablist" aria-label="Painel">
|
|
<button class="tab-button active" id="tabBtnProjeto" role="tab" aria-selected="true" aria-controls="tabProjeto" onclick="switchTab('projeto')" type="button">
|
|
<span class="tab-icon" aria-hidden="true">▣</span>Projeto
|
|
</button>
|
|
<button class="tab-button" id="tabBtnScanner" role="tab" aria-selected="false" aria-controls="tabScanner" onclick="switchTab('scanner')" type="button">
|
|
<span class="tab-icon" aria-hidden="true">◉</span>Scanner
|
|
</button>
|
|
<button class="tab-button" id="tabBtnTrilhas" role="tab" aria-selected="false" aria-controls="tabTrilhas" onclick="switchTab('trilhas')" type="button">
|
|
<span class="tab-icon" aria-hidden="true">♫</span>Trilhas
|
|
</button>
|
|
<button class="tab-button" id="tabBtnRefinar" role="tab" aria-selected="false" aria-controls="tabRefinar" onclick="switchTab('refinar')" type="button">
|
|
<span class="tab-icon" aria-hidden="true">◌</span>Refinar
|
|
</button>
|
|
<button class="tab-button" id="tabBtnSilence" role="tab" aria-selected="false" aria-controls="tabSilence" onclick="switchTab('silence')" type="button">
|
|
<span class="tab-icon" aria-hidden="true">✂</span>Editar vídeo
|
|
</button>
|
|
<button class="tab-button" id="tabBtnRetakes" role="tab" aria-selected="false" aria-controls="tabRetakes" onclick="switchTab('retakes')" type="button">
|
|
<span class="tab-icon" aria-hidden="true">↻</span>Retakes
|
|
</button>
|
|
<button class="tab-button" id="tabBtnAnalises" role="tab" aria-selected="false" aria-controls="tabAnalises" onclick="switchTab('analises')" type="button">
|
|
<span class="tab-icon" aria-hidden="true">⎘</span>Análises
|
|
</button>
|
|
<button class="tab-button" id="tabBtnTiposVideo" role="tab" aria-selected="false" aria-controls="tabTiposVideo" onclick="switchTab('tipos-video')" type="button">
|
|
<span class="tab-icon" aria-hidden="true">▦</span>Tipos de vídeo
|
|
</button>
|
|
<button class="tab-button" id="tabBtnBridge" role="tab" aria-selected="false" aria-controls="tabBridge" onclick="switchTab('bridge')" type="button">
|
|
<span class="tab-icon" aria-hidden="true">⇄</span>Conexão
|
|
</button>
|
|
<button class="tab-button" id="tabBtnModels" role="tab" aria-selected="false" aria-controls="tabModels" onclick="switchTab('models')" type="button">
|
|
<span class="tab-icon" aria-hidden="true">◈</span>Modelos
|
|
</button>
|
|
<button class="tab-button" id="tabBtnSettings" role="tab" aria-selected="false" aria-controls="tabSettings" onclick="switchTab('settings')" type="button">
|
|
<span class="tab-icon" aria-hidden="true">⚙</span>Ajustes
|
|
</button>
|
|
<button class="tab-button" id="tabBtnTestarAcoes" role="tab" aria-selected="false" aria-controls="tabTestarAcoes" onclick="switchTab('testar-acoes')" type="button">
|
|
<span class="tab-icon" aria-hidden="true">⚗</span>Testar ações
|
|
</button>
|
|
<button class="tab-button" id="tabBtnLegendas" role="tab" aria-selected="false" aria-controls="tabLegendas" onclick="switchTab('legendas')" type="button">
|
|
<span class="tab-icon" aria-hidden="true">▤</span>Legendas
|
|
</button>
|
|
</nav>
|
|
|
|
<!-- O painel é reposicionado para a aba que iniciou a operação. -->
|
|
<section class="task-dock" id="taskDock" role="status" aria-live="polite" hidden>
|
|
<div class="task-head">
|
|
<span class="task-spinner" id="taskSpinner" aria-hidden="true"></span>
|
|
<strong class="task-title" id="taskTitle">Processando…</strong>
|
|
<span class="task-elapsed" id="taskElapsed">00:00</span>
|
|
<button type="button" class="task-cancel" id="taskCancel" onclick="taskCancel()" hidden>Cancelar</button>
|
|
<button type="button" class="task-copy" id="taskCopy" onclick="taskCopyError()" aria-label="Copiar erro técnico" title="Copiar erro técnico" hidden>⧉</button>
|
|
</div>
|
|
<div class="progress-track" id="taskTrack" data-mode="indeterminate">
|
|
<div class="progress-fill" id="taskFill" style="width:0%"></div>
|
|
</div>
|
|
<div class="task-foot">
|
|
<span class="task-stage" id="taskStage">Preparando…</span>
|
|
<span class="task-pct" id="taskPct"></span>
|
|
</div>
|
|
<pre class="task-error-details" id="taskErrorDetails" hidden></pre>
|
|
</section>
|
|
|
|
<div class="toast" id="toast" role="status" aria-live="polite" hidden>
|
|
<span class="toast-icon" id="toastIcon" aria-hidden="true">✓</span>
|
|
<span class="toast-text" id="toastText"></span>
|
|
<button type="button" class="toast-close" onclick="hideToast()" aria-label="Fechar aviso">✕</button>
|
|
</div>
|
|
|
|
<!-- ================================ PROJETO ================================ -->
|
|
<div class="tab-panel" id="tabProjeto" role="tabpanel" aria-labelledby="tabBtnProjeto">
|
|
<p class="tab-intro">Um projeto contém uma única sequência e pode usar várias faixas, clipes e arquivos de mídia.</p>
|
|
<section class="card">
|
|
<div class="card-head"><h2>Sequência do projeto</h2><span class="card-kicker" id="projetoStatus">Aguardando seleção</span></div>
|
|
<button class="button button-primary" id="btnProjetoCarregar" onclick="projetoListarSequencias()" type="button">Carregar sequências do Premiere</button>
|
|
<label class="field-label" for="projetoSequenciaSelect">Sequência / timeline</label>
|
|
<select id="projetoSequenciaSelect" class="select-field" onchange="projetoSelecionarSequencia()" disabled>
|
|
<option value="">Nenhuma sequência carregada</option>
|
|
</select>
|
|
<p class="field-help" id="projetoInfo">Escolha a sequência que será a raiz deste projeto.</p>
|
|
</section>
|
|
<section class="card" id="projetoTimelineCard" hidden>
|
|
<div class="card-head"><h2>Informações da timeline</h2><span class="card-kicker" id="projetoTimelineStatus">—</span></div>
|
|
<dl class="kv">
|
|
<div><dt>Duração</dt><dd id="projetoDuracao">—</dd></div>
|
|
<div><dt>Resolução</dt><dd id="projetoResolucao">—</dd></div>
|
|
<div><dt>Faixas</dt><dd id="projetoTotalFaixas">—</dd></div>
|
|
<div><dt>Clipes</dt><dd id="projetoTotalClipes">—</dd></div>
|
|
</dl>
|
|
<label class="field-label" for="projetoTipoVideoSelect">Tipo de vídeo</label>
|
|
<select id="projetoTipoVideoSelect" class="select-field" onchange="projetoSelecionarTipoVideo()" disabled>
|
|
<option value="">Carregando tipo de vídeo…</option>
|
|
</select>
|
|
<p class="field-help" id="projetoTipoVideoInfo">O tipo definido na edição concluída será carregado do banco.</p>
|
|
<h3>Faixas e clipes utilizados</h3>
|
|
<div id="projetoFaixas" class="scanner-track-list"></div>
|
|
<div class="button-row">
|
|
<button class="button button-primary" id="btnProjetoSalvar" onclick="projetoSalvar()" type="button" disabled>Salvar projeto no banco</button>
|
|
<button class="button button-primary" id="btnProjetoContinuar" onclick="projetoContinuarEdicao()" type="button" disabled>Continuar para editar vídeo</button>
|
|
</div>
|
|
</section>
|
|
</div>
|
|
|
|
<!-- ============================= EDITAR VÍDEO ============================= -->
|
|
<div class="tab-panel" id="tabSilence" role="tabpanel" aria-labelledby="tabBtnSilence" hidden>
|
|
|
|
<p class="tab-intro">Os dados analisados e o tipo de vídeo definidos no Projeto já estão disponíveis para esta edição.</p>
|
|
|
|
<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">A análise mais recente da sequência será carregada automaticamente.</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>
|
|
</section>
|
|
|
|
<section class="step" id="step1" data-state="locked" hidden>
|
|
<div class="step-head">
|
|
<span class="step-number" aria-hidden="true">1</span>
|
|
<div class="step-title">
|
|
<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">Aguardando análise</span>
|
|
</div>
|
|
<div class="step-body">
|
|
<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>
|
|
|
|
<section class="step" id="step2" data-state="locked">
|
|
<div class="step-head">
|
|
<span class="step-number" aria-hidden="true">2</span>
|
|
<div class="step-title">
|
|
<h2>Revisar plano de edição</h2>
|
|
<p>Carrega do banco o plano criado para esta sequência e mostra as decisões antes da aplicação.</p>
|
|
</div>
|
|
<span class="step-chip" id="step2Chip">Aguardando plano</span>
|
|
</div>
|
|
<div class="step-body">
|
|
<p class="field-help" id="planoResumoEdicao">O plano editorial pendente será carregado automaticamente da sequência selecionada.</p>
|
|
<button id="btnBuildPlan" class="button button-primary" onclick="carregarPlanoEditorialSalvo()" type="button" disabled>Carregar plano salvo</button>
|
|
<dl class="kv" id="silenceJsonCard" hidden>
|
|
<div><dt>Trechos</dt><dd id="jsonSegmentCount">—</dd></div>
|
|
<div><dt>Arquivo</dt><dd class="kv-path" id="jsonOutputPath">—</dd></div>
|
|
</dl>
|
|
<div class="button-row json-file-actions" id="jsonFileActions" hidden>
|
|
<button class="button button-ghost" id="btnOpenJsonFolder" onclick="abrirPastaDoJsonGerado()" type="button">Abrir pasta no Finder</button>
|
|
<button class="button button-ghost" id="btnCopyJsonPath" onclick="copiarCaminhoDoJsonGerado()" type="button">Copiar caminho</button>
|
|
</div>
|
|
<p class="field-help" id="silencePlanInfo"></p>
|
|
</div>
|
|
</section>
|
|
|
|
<section class="step" id="step3" data-state="locked">
|
|
<div class="step-head">
|
|
<span class="step-number" aria-hidden="true">3</span>
|
|
<div class="step-title">
|
|
<h2>Aplicar na timeline</h2>
|
|
<p>Selecione o plano de edição que a IA devolveu (cortes, zooms, textos, marcadores) e execute na sequência.</p>
|
|
</div>
|
|
<span class="step-chip" id="step3Chip">Bloqueado</span>
|
|
</div>
|
|
<div class="step-body">
|
|
<label class="field-label" for="editorialPlanJson">Plano carregado do banco</label>
|
|
<p class="field-help" id="editorialSavedPlanStatus">O plano será lido diretamente do banco da edição concluída.</p>
|
|
<textarea id="editorialPlanJson" class="editorial-plan-json" rows="10" spellcheck="false" placeholder='Carregue do banco o JSON com source e actions.'></textarea>
|
|
<p class="field-help" id="editorialPlanJsonStatus">Carregue o plano salvo no SQLite. O painel valida frases, palavras e margens antes de executar.</p>
|
|
<label class="field-label" for="editorialPlanPath">Arquivo do plano (JSON devolvido pela IA)</label>
|
|
<div class="file-field">
|
|
<input type="text" id="editorialPlanPath" spellcheck="false" autocomplete="off" placeholder="/caminho/para/plano.json">
|
|
<button type="button" class="button button-ghost" id="btnPickPlanFile" onclick="pickEditorialPlanFile()">Procurar…</button>
|
|
</div>
|
|
<div class="callout callout-warn" id="silenceApplyInfo">
|
|
<strong>Sem desfazer</strong>
|
|
<p>Esta versão do Premiere não expõe undo para o painel. Um backup da sequência é criado automaticamente antes de aplicar.</p>
|
|
</div>
|
|
<button id="btnApplyEditorialActions" class="button button-danger" onclick="applyEditorialActions()" type="button" disabled>Aplicar plano na timeline</button>
|
|
</div>
|
|
</section>
|
|
|
|
<details class="log-details">
|
|
<summary>Detalhes técnicos <span class="log-hint">(o passo a passo completo)</span></summary>
|
|
<div id="silenceLog" role="log" aria-live="polite" aria-relevant="additions" tabindex="0" aria-label="Log da edição"></div>
|
|
</details>
|
|
|
|
</div>
|
|
|
|
<!-- ============================== SCANNER =============================== -->
|
|
<div class="tab-panel" id="tabScanner" role="tabpanel" aria-labelledby="tabBtnScanner">
|
|
<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-existing-analysis">
|
|
<div class="card-head"><h2>Análises já realizadas</h2><span class="card-kicker">Banco de dados</span></div>
|
|
<div class="field-grid"><div><label class="field-label" for="scannerExistingAnalysis">Selecione uma análise salva</label><select id="scannerExistingAnalysis" class="select-field" onchange="scannerSelecionarAnaliseExistente()"><option value="">Nenhuma análise carregada</option></select></div><div><span class="field-label">Estado</span><p class="field-static" id="scannerExistingAnalysisStatus">Verificando o banco…</p></div></div>
|
|
<p class="field-help" id="scannerExistingAnalysisInfo">As análises concluídas ficam disponíveis para a tela Editar vídeo.</p>
|
|
</section>
|
|
<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" 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>
|
|
<div class="range-field-row">
|
|
<input id="scannerInterval" type="range" min="0.2" max="10" step="0.1" value="1" oninput="scannerUpdateIntervalLabel()">
|
|
<span class="range-field-value" id="scannerIntervalValue">1 s/quadro</span>
|
|
</div>
|
|
<p class="field-help">Quanto menor, mais frames e mais detalhado (e mais lento). Valores maiores aceleram a análise, principalmente em clipes curtos.</p>
|
|
<label class="field-label" for="scannerResolution">Resolução da análise</label>
|
|
<select id="scannerResolution" class="select-field">
|
|
<option value="0">Original — sem redimensionar</option>
|
|
<option value="1080" selected>1080p — recomendado</option>
|
|
<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>
|
|
<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>
|
|
<label class="checkbox-field"><input type="checkbox" data-scanner-resource="pessoas" checked><span>Pessoas</span></label>
|
|
<label class="checkbox-field"><input type="checkbox" data-scanner-resource="pose" checked><span>Pose e mãos</span></label>
|
|
<label class="checkbox-field"><input type="checkbox" data-scanner-resource="ocr" checked><span>Texto e códigos</span></label>
|
|
<label class="checkbox-field"><input type="checkbox" data-scanner-resource="categorias" checked><span>Objetos e categorias</span></label>
|
|
<label class="checkbox-field"><input type="checkbox" data-scanner-resource="estetica" checked><span>Estética, horizonte e geometria</span></label>
|
|
<label class="checkbox-field"><input type="checkbox" data-scanner-resource="segmentacao_de_pessoas" checked><span>Segmentação de pessoas</span></label>
|
|
</div>
|
|
<div class="scanner-subsection"><div class="card-head"><h3>Leitura temporal</h3></div>
|
|
<label class="checkbox-field"><input id="scannerScenes" type="checkbox" checked><span>Agrupar cenas</span></label>
|
|
<label class="checkbox-field"><input id="scannerContinuity" type="checkbox" checked><span>Medir continuidade e cortes</span></label>
|
|
<label class="checkbox-field"><input id="scannerReframe" type="checkbox"><span>Guardar geometria para reenquadramento</span></label>
|
|
<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" 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>
|
|
<div class="scanner-subsection"><div class="card-head"><h3>Transcrição</h3><span class="card-kicker" id="scannerModelCount">—</span></div>
|
|
<div class="field-grid"><div><label class="field-label" for="scannerTranscribeModel">Modelo instalado</label><select id="scannerTranscribeModel" class="select-field"></select></div><div><label class="field-label" for="scannerTranscribeLanguage">Idioma</label><select id="scannerTranscribeLanguage" class="select-field"></select></div></div>
|
|
<div class="scanner-option-row"><label class="checkbox-field"><input id="scannerDiarization" type="checkbox"><span>Identificar locutores após transcrever</span></label><span class="scanner-analysis-status" id="scannerDiarizationStatus">Ainda não processado</span></div>
|
|
<div class="scanner-option-row"><label class="checkbox-field"><input id="scannerVoiceFeatures" type="checkbox"><span>Calcular métricas da fala</span></label><span class="scanner-analysis-status" id="scannerVoiceFeaturesStatus">Ainda não processado</span></div>
|
|
<p class="field-help">Mede pitch, energia, velocidade de fala e pausas por trecho. Não classifica emoção e tem custo baixo comparado à transcrição.</p>
|
|
<div class="hf-token-status" id="scannerHfStatus" data-state="unset"><span class="check-dot" aria-hidden="true"></span><span id="scannerHfStatusText">Hugging Face não configurado</span><button class="link-button" type="button" onclick="switchTab('settings')">Configurar</button></div>
|
|
<p class="field-help">A lista inclui somente modelos encontrados nas pastas locais configuradas. O token do Hugging Face é usado apenas na diarização; nunca é salvo no perfil nem no relatório.</p>
|
|
</div>
|
|
</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 á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>
|
|
</div>
|
|
<!-- =============================== TRILHAS =============================== -->
|
|
<div class="tab-panel" id="tabTrilhas" role="tabpanel" aria-labelledby="tabBtnTrilhas" hidden>
|
|
<p class="tab-intro">Organize e analise músicas instrumentais para usar como fundo nos projetos de vídeo.</p>
|
|
<section class="card">
|
|
<div class="card-head"><h2>Pasta de trilhas musicais</h2><span class="card-kicker">Catálogo local</span></div>
|
|
<div class="file-field">
|
|
<input type="text" id="trilhasPasta" spellcheck="false" autocomplete="off" placeholder="Selecione uma pasta de músicas" oninput="trilhasAtualizarEstado()">
|
|
<button type="button" class="button button-ghost" onclick="trilhasSelecionarPasta()">Selecionar…</button>
|
|
</div>
|
|
<p class="field-help" id="trilhasPastaStatus">O caminho será salvo no banco para reutilizar esta pasta depois.</p>
|
|
<p class="field-help">A análise usa os modelos Essentia instalados internamente pela aplicação. Você não precisa localizar nem selecionar essa pasta.</p>
|
|
<button class="button button-primary" id="btnTrilhasAnalisar" onclick="trilhasAnalisar()" type="button" disabled>Analisar trilhas</button>
|
|
</section>
|
|
<section class="card" id="trilhasProgressoCard" hidden>
|
|
<div class="card-head"><h2>Progresso da análise</h2><span class="card-kicker" id="trilhasContagem">0 trilhas</span></div>
|
|
<div id="trilhasLista" class="trilhas-lista" role="list" aria-live="polite"></div>
|
|
</section>
|
|
</div>
|
|
|
|
<!-- =============================== REFINAR ============================== -->
|
|
<div class="tab-panel" id="tabRefinar" role="tabpanel" aria-labelledby="tabBtnRefinar" hidden>
|
|
<p class="tab-intro">Confirme quem são os falantes antes de editar. O preview reproduz somente os trechos identificados de cada pessoa.</p>
|
|
<section class="card scanner-analysis-card">
|
|
<div class="card-head"><h2>Falantes da análise</h2><span class="step-chip" id="refinarStatus">Aguardando análise</span></div>
|
|
<p class="field-help" id="refinarInfo">Carregue a análise mais recente do Scanner para consultar os falantes detectados.</p>
|
|
<div class="refinar-actions">
|
|
<button id="btnRefinarLoad" class="button button-ghost" onclick="refinarCarregarAnalise()" type="button">Recarregar análise do Scanner</button>
|
|
<button id="btnRefinarStopAudio" class="button" onclick="stopSpeakerAudio()" type="button">Parar preview</button>
|
|
</div>
|
|
</section>
|
|
<section class="card" id="refinarSpeakerCard">
|
|
<div class="card-head"><h2>Identificar falantes</h2><span class="card-kicker" id="refinarSpeakerCount">Nenhum falante carregado</span></div>
|
|
<p class="field-help">Use o botão de áudio para reconhecer a voz de cada falante, informe um nome e escolha quem poderá aparecer no vídeo. A identificação será usada ao gerar o JSON de edição.</p>
|
|
<div class="callout callout-info" id="refinarSpeakerEmpty"><strong>Nenhum falante identificado</strong><p>A análise carregada não possui diarização. Execute a diarização no Scanner para liberar esta etapa.</p></div>
|
|
<div class="speaker-config" id="refinarSpeakerConfig" hidden>
|
|
<div id="refinarSpeakerList"></div>
|
|
<button id="btnRefinarSave" class="button button-primary" onclick="refinarSalvarFalantes()" type="button">Salvar identificação</button>
|
|
</div>
|
|
</section>
|
|
</div>
|
|
|
|
<!-- ============================== RETAKES ============================== -->
|
|
<div class="tab-panel" id="tabRetakes" role="tabpanel" aria-labelledby="tabBtnRetakes" hidden>
|
|
<p class="tab-intro">Identifica repetições e tentativas alternativas usando a transcrição do Scanner. O painel nunca corta o vídeo: você revisa e decide.</p>
|
|
|
|
<!-- 1. Arquivo ou conjunto analisado -->
|
|
<section class="card">
|
|
<div class="card-head"><h2>Arquivo analisado</h2><span class="card-kicker" id="retakesStatusGeral">—</span></div>
|
|
<div class="field-grid">
|
|
<div><label class="field-label" for="retakesCaso">Caso do Scanner</label>
|
|
<select id="retakesCaso" class="select-field" onchange="retakesCarregarCaso()"></select></div>
|
|
<div><button class="button button-ghost" onclick="retakesListarCasos()" type="button">Atualizar lista</button></div>
|
|
</div>
|
|
<p class="field-help" id="retakesInfoCaso">Selecione um caso já processado pelo Scanner.</p>
|
|
</section>
|
|
|
|
<!-- 2/3. Configurações e critérios -->
|
|
<div class="scanner-options-grid">
|
|
<section class="card"><div class="card-head"><h2>Critérios</h2></div>
|
|
<label class="field-label" for="retakesCriterio">Critério de confirmação</label>
|
|
<select id="retakesCriterio" class="select-field">
|
|
<option value="conservador">Conservador — só evidências fortes</option>
|
|
<option value="equilibrado" selected>Equilibrado — prováveis e fortes</option>
|
|
<option value="sensivel">Sensível — também possíveis</option>
|
|
</select>
|
|
<label class="checkbox-field"><input id="retakesReaproveitar" type="checkbox" checked><span>Reaproveitar análise existente</span></label>
|
|
<p class="field-help">A comparação textual, os reinícios de fala e as pausas já fazem parte da análise da engine.</p>
|
|
</section>
|
|
<section class="card"><div class="card-head"><h2>Análise visual auxiliar</h2></div>
|
|
<label class="checkbox-field"><input id="retakesUsarVisual" type="checkbox"><span>Usar cortes visuais como evidência</span></label>
|
|
<p class="field-help">Reforça a confiança, mas nunca decide sozinha. Reutiliza as cenas já analisadas.</p>
|
|
<div class="button-row">
|
|
<button class="button button-primary" id="btnRetakesAnalisar" onclick="retakesAnalisar()" type="button">Analisar retakes</button>
|
|
</div>
|
|
</section>
|
|
</div>
|
|
|
|
<!-- 4. Progresso -->
|
|
<div class="callout callout-info" id="retakesProgressoCard" hidden>
|
|
<strong id="retakesProgressoEtapa">Analisando retakes…</strong>
|
|
<p id="retakesProgressoDetalhe">—</p>
|
|
</div>
|
|
|
|
<!-- 5/6. Resultados e revisão -->
|
|
<section class="card">
|
|
<div class="card-head"><h2>Resultados</h2><span class="card-kicker" id="retakesContagem">—</span></div>
|
|
<label class="field-label" for="retakesFiltro">Filtrar</label>
|
|
<select id="retakesFiltro" class="select-field" onchange="retakesRenderResultados()">
|
|
<option value="todos" selected>Todos</option>
|
|
<option value="fortes">Fortes</option>
|
|
<option value="possiveis">Possíveis e prováveis</option>
|
|
<option value="pendente_de_revisao">Pendentes de revisão</option>
|
|
<option value="confirmado">Confirmados</option>
|
|
<option value="nao_e_retake">Rejeitados</option>
|
|
<option value="ignorado">Ignorados</option>
|
|
</select>
|
|
<div id="retakesResultados" role="list" aria-label="Grupos de retakes"></div>
|
|
<p class="field-help" id="retakesSemResultados" hidden>Nenhum retake identificado com os critérios atuais.</p>
|
|
</section>
|
|
<div id="retakesLog" role="log" aria-live="polite" aria-relevant="additions" tabindex="0" aria-label="Log de retakes"></div>
|
|
</div>
|
|
|
|
<!-- ============================== ANÁLISES ============================= -->
|
|
<div class="tab-panel" id="tabAnalises" role="tabpanel" aria-labelledby="tabBtnAnalises" hidden>
|
|
<p class="tab-intro">Lê direto do banco de análises o J-SOM de imagem (evidências visuais) e o de som (transcrição por cena). Nada é reprocessado.</p>
|
|
<section class="card">
|
|
<div class="card-head"><h2>Banco de análises</h2><span class="card-kicker" id="analisesStatus">—</span></div>
|
|
<p class="field-help" id="analisesCaminho">—</p>
|
|
<div class="button-row">
|
|
<button class="button button-primary" id="btnAnalisesCarregar" onclick="analisesCarregar()" type="button">Carregar do banco</button>
|
|
<button class="button button-ghost" onclick="analisesAbrirJson()" type="button" disabled id="btnAnalisesAbrir">Abrir JSON</button>
|
|
<button class="button button-ghost" onclick="analisesCopiar()" type="button" disabled id="btnAnalisesCopiar">Copiar conteúdo</button>
|
|
</div>
|
|
</section>
|
|
<section class="card" id="analisesResumoCard" hidden>
|
|
<div class="card-head"><h2 id="analisesVideoNome">—</h2><span class="card-kicker" id="analisesResumo">—</span></div>
|
|
<p class="field-help">Som: transcrição com emoção por fala. Imagem: evidências Apple Vision por clipe. “Cena” sem marca indica clipe sem corte de cena gravado.</p>
|
|
<div id="analisesCenas" role="list" aria-label="Cenas com transcrição e evidências"></div>
|
|
</section>
|
|
</div>
|
|
|
|
<!-- =========================== TIPOS DE VÍDEO =========================== -->
|
|
<div class="tab-panel" id="tabTiposVideo" role="tabpanel" aria-labelledby="tabBtnTiposVideo" hidden>
|
|
<p class="tab-intro">Cadastre o padrão editorial que a IA e o editor devem seguir para cada tipo de vídeo. As alterações ficam salvas no catálogo local do Schedule Light.</p>
|
|
|
|
<section class="card">
|
|
<div class="card-head">
|
|
<h2>Catálogo de tipos</h2>
|
|
<button class="link-button" onclick="tipoVideoNovo()" type="button">+ Novo tipo</button>
|
|
</div>
|
|
<div id="tiposVideoLista" class="video-type-list" role="list" aria-label="Tipos de vídeo cadastrados"></div>
|
|
</section>
|
|
|
|
<section class="card" id="tipoVideoEditorCard" hidden>
|
|
<div class="card-head"><h2 id="tipoVideoEditorTitulo">Novo tipo de vídeo</h2><span class="card-kicker" id="tipoVideoEditorStatus">Rascunho</span></div>
|
|
<section class="profile-section"><div class="profile-section-head"><span>1</span><div><strong>Identificação</strong><small>Nome, categoria e status do perfil.</small></div></div>
|
|
<label class="field-label" for="tipoVideoNome">Nome do perfil</label><input class="text-field" id="tipoVideoNome" type="text" placeholder="Ex.: Depoimento de cliente">
|
|
<label class="field-label" for="tipoVideoDescricao">Descrição</label><textarea id="tipoVideoDescricao" rows="2" placeholder="Para que este vídeo existe e qual resultado deve provocar?"></textarea>
|
|
<div class="field-grid"><div><label class="field-label" for="tipoVideoCategoria">Categoria</label><select class="select-field" id="tipoVideoCategoria"><option>Depoimento</option><option>Entrevista</option><option>Storytelling</option><option>Educativo</option><option>Comercial</option><option>Institucional</option></select></div><div><label class="field-label" for="tipoVideoAtivo">Status</label><label class="checkbox-field"><input id="tipoVideoAtivo" type="checkbox"><span>Perfil ativo</span></label></div></div>
|
|
</section>
|
|
<section class="profile-section"><div class="profile-section-head"><span>2</span><div><strong>Objetivo</strong><small>O resultado que o vídeo deve provocar.</small></div></div>
|
|
<label class="field-label" for="tipoVideoObjetivo">Objetivo principal</label><input class="text-field" id="tipoVideoObjetivo" type="text" placeholder="Ex.: Gerar confiança e credibilidade">
|
|
<label class="field-label" for="tipoVideoObjetivoSecundario">Objetivo secundário</label><input class="text-field" id="tipoVideoObjetivoSecundario" type="text" placeholder="Ex.: Criar conexão emocional">
|
|
<label class="field-label" for="tipoVideoPublico">Público-alvo</label><input class="text-field" id="tipoVideoPublico" type="text" placeholder="Ex.: Clientes em potencial">
|
|
<label class="field-label" for="tipoVideoMensagem">Mensagem principal</label><textarea id="tipoVideoMensagem" rows="2" placeholder="Qual mensagem precisa permanecer depois que o vídeo terminar?"></textarea>
|
|
</section>
|
|
<section class="profile-section"><div class="profile-section-head"><span>3</span><div><strong>Narrativa</strong><small>Como a história deve ser organizada.</small></div></div>
|
|
<label class="field-label" for="tipoVideoTipoNarrativa">Tipo de narrativa</label><select class="select-field" id="tipoVideoTipoNarrativa"><option>História pessoal</option><option>Pergunta e resposta</option><option>Jornada / transformação</option><option>Explicação passo a passo</option><option>Problema → solução → benefício</option></select>
|
|
<label class="field-label" for="tipoVideoFocoNarrativo">Foco narrativo</label><input class="text-field" id="tipoVideoFocoNarrativo" type="text" placeholder="Ex.: Problema → solução → resultado">
|
|
<label class="checkbox-field"><input id="tipoVideoStorytelling" type="checkbox"><span>Priorizar storytelling</span></label>
|
|
<label class="field-label" for="tipoVideoEstrutura">Estrutura narrativa</label><textarea id="tipoVideoEstrutura" rows="3" placeholder="Ex.: Gancho → contexto → desenvolvimento → conclusão."></textarea>
|
|
</section>
|
|
<section class="profile-section"><div class="profile-section-head"><span>4</span><div><strong>Emoção</strong><small>Qual sensação deve ser construída.</small></div></div>
|
|
<label class="field-label" for="tipoVideoEmocao">Objetivo emocional</label><input class="text-field" id="tipoVideoEmocao" type="text" placeholder="Confiança, empatia, curiosidade…">
|
|
<label class="field-label" for="tipoVideoIntensidade">Intensidade emocional <output id="tipoVideoIntensidadeValor">3 / 5</output></label><input id="tipoVideoIntensidade" type="range" min="1" max="5" step="1" value="3" oninput="tipoVideoAtualizarIntensidade()">
|
|
<label class="field-label" for="tipoVideoTom">Tom emocional</label><select class="select-field" id="tipoVideoTom"><option>Positivo</option><option>Inspirador</option><option>Íntimo</option><option>Informativo</option><option>Urgente</option><option>Contemplativo</option></select>
|
|
<label class="field-label" for="tipoVideoConstrucaoEmocao">Como construir a emoção</label><textarea id="tipoVideoConstrucaoEmocao" rows="2" placeholder="Ex.: Pausas naturais, B-roll real e trilha discreta."></textarea>
|
|
</section>
|
|
<section class="profile-section"><div class="profile-section-head"><span>5</span><div><strong>Formato</strong><small>Onde e em quanto tempo o vídeo será consumido.</small></div></div>
|
|
<label class="field-label" for="tipoVideoCanais">Formato / canal</label><input class="text-field" id="tipoVideoCanais" type="text" placeholder="Instagram Reels, TikTok, YouTube…">
|
|
<label class="field-label" for="tipoVideoProporcao">Orientação preferencial</label><select class="select-field" id="tipoVideoProporcao"><option value="9:16">9:16 — vertical</option><option value="1:1">1:1 — quadrado</option><option value="16:9">16:9 — horizontal</option><option value="adaptavel">Adaptável</option></select>
|
|
<label class="field-label" for="tipoVideoRitmo">Ritmo de edição</label><select class="select-field" id="tipoVideoRitmo"><option value="agil">Ágil</option><option value="equilibrado">Equilibrado</option><option value="calmo">Calmo</option><option value="progressivo">Progressivo</option></select>
|
|
<div class="field-grid"><div><label class="field-label" for="tipoVideoDuracaoMinima">Duração mínima (segundos)</label><input class="text-field" id="tipoVideoDuracaoMinima" type="number" min="0" step="1"></div><div><label class="field-label" for="tipoVideoDuracao">Duração máxima (segundos)</label><input class="text-field" id="tipoVideoDuracao" type="number" min="1" step="1"></div></div>
|
|
</section>
|
|
<section class="profile-section"><div class="profile-section-head"><span>6</span><div><strong>Prioridades</strong><small>Ordem de importância quando houver conflito.</small></div></div>
|
|
<div id="tipoVideoPrioridades" class="priority-list"></div>
|
|
</section>
|
|
<section class="profile-section"><div class="profile-section-head"><span>7</span><div><strong>Elementos de edição</strong><small>O que deve ou não aparecer na montagem.</small></div></div>
|
|
<label class="checkbox-field"><input id="tipoVideoUsaBroll" type="checkbox"><span>Usar B-roll</span></label><label class="checkbox-field"><input id="tipoVideoUsaLegendas" type="checkbox"><span>Usar legendas</span></label><label class="checkbox-field"><input id="tipoVideoUsaTextos" type="checkbox"><span>Usar textos e títulos</span></label><label class="checkbox-field"><input id="tipoVideoUsaGraficos" type="checkbox"><span>Usar gráficos ou telas explicativas</span></label><label class="checkbox-field"><input id="tipoVideoUsaZoom" type="checkbox"><span>Usar zooms e reenquadramentos</span></label>
|
|
<label class="field-label" for="tipoVideoCortes">Critério de cortes</label><textarea id="tipoVideoCortes" rows="3" placeholder="O que cortar, preservar ou acelerar?"></textarea>
|
|
<label class="field-label" for="tipoVideoBroll">Orientação de B-roll</label><textarea id="tipoVideoBroll" rows="3" placeholder="Quando usar imagens de apoio e com qual função?"></textarea>
|
|
<label class="field-label" for="tipoVideoTexto">Textos, títulos e legendas</label><textarea id="tipoVideoTexto" rows="3" placeholder="Legendas, palavras-chave, títulos, CTA…"></textarea>
|
|
</section>
|
|
<section class="profile-section"><div class="profile-section-head"><span>8</span><div><strong>Áudio</strong><small>Como a voz, música e efeitos devem conviver.</small></div></div>
|
|
<label class="checkbox-field"><input id="tipoVideoUsaMusica" type="checkbox"><span>Usar música</span></label><label class="field-label" for="tipoVideoIntensidadeMusica">Intensidade da música</label><input id="tipoVideoIntensidadeMusica" type="range" min="0" max="100" step="5" value="30" oninput="tipoVideoAtualizarIntensidadeMusica()"><output id="tipoVideoIntensidadeMusicaValor">30%</output>
|
|
<label class="field-label" for="tipoVideoPrioridadeVoz">Prioridade da voz</label><select class="select-field" id="tipoVideoPrioridadeVoz"><option>Alta</option><option>Muito alta</option><option>Equilibrada</option></select>
|
|
<label class="field-label" for="tipoVideoSom">Tratamento de áudio</label><textarea id="tipoVideoSom" rows="3" placeholder="Trilha, volume, pausas, limpeza e ênfase."></textarea>
|
|
</section>
|
|
<section class="profile-section"><div class="profile-section-head"><span>9</span><div><strong>Regras e instruções para IA</strong><small>Critérios que devem orientar o plano de edição.</small></div></div>
|
|
<label class="field-label" for="tipoVideoDiretriz">Regras de edição</label><textarea id="tipoVideoDiretriz" rows="5" placeholder="Uma regra por linha. Ex.: Preservar a naturalidade da fala."></textarea>
|
|
<label class="field-label" for="tipoVideoRestricoes">Restrições de edição</label><textarea id="tipoVideoRestricoes" rows="3" placeholder="O que a IA não deve fazer?"></textarea>
|
|
<label class="field-label" for="tipoVideoQualidade">Critérios de qualidade</label><textarea id="tipoVideoQualidade" rows="3" placeholder="Como saberemos que o resultado ficou bom?"></textarea>
|
|
</section>
|
|
<div class="button-row">
|
|
<button class="button button-primary" onclick="tipoVideoSalvar()" type="button">Salvar tipo</button>
|
|
<button class="button button-ghost" onclick="tipoVideoCancelar()" type="button">Cancelar</button>
|
|
<button class="button button-ghost" onclick="tipoVideoCopiarJson()" type="button">Copiar JSON para IA</button>
|
|
</div>
|
|
</section>
|
|
<p class="field-help" id="tiposVideoStatus">—</p>
|
|
</div>
|
|
|
|
|
|
|
|
<!-- ============================== CONEXÃO ============================== -->
|
|
<div class="tab-panel" id="tabBridge" role="tabpanel" aria-labelledby="tabBtnBridge" hidden>
|
|
|
|
<section class="status-panel" role="status" aria-live="polite" aria-atomic="true">
|
|
<div class="status-indicator" aria-hidden="true">
|
|
<div class="status-ring"><div class="status-dot waiting" id="statusDot"></div></div>
|
|
</div>
|
|
<div class="status-copy">
|
|
<span class="section-label">Status da ponte</span>
|
|
<strong id="statusText">Iniciando conector…</strong>
|
|
<span id="statusDetail">Verificando o Premiere Pro</span>
|
|
</div>
|
|
<div class="command-stat">
|
|
<strong id="cmdCount">0</strong>
|
|
<span>comandos</span>
|
|
</div>
|
|
</section>
|
|
|
|
<div class="action-row">
|
|
<button id="btnStart" class="button button-primary" onclick="startBridge()" type="button" aria-controls="log">
|
|
<svg viewBox="0 0 20 20" aria-hidden="true"><path class="fill-icon" d="m7 5 8 5-8 5V5Z"/></svg>
|
|
Iniciar
|
|
</button>
|
|
<button id="btnStop" class="button button-danger" onclick="stopBridge()" type="button" aria-controls="log" disabled>
|
|
<svg viewBox="0 0 20 20" aria-hidden="true"><rect class="fill-icon" x="6" y="6" width="8" height="8" rx="1"/></svg>
|
|
Parar
|
|
</button>
|
|
</div>
|
|
|
|
<section class="card" aria-labelledby="connectionCenterTitle">
|
|
<div class="card-head">
|
|
<h2 id="connectionCenterTitle">Pronto para editar?</h2>
|
|
<button class="link-button" type="button" onclick="refreshConnectionCenter()" aria-controls="connectionChecks">Verificar</button>
|
|
</div>
|
|
<ul class="connection-checks" id="connectionChecks">
|
|
<li id="checkConnector" data-state="waiting"><span class="check-dot" aria-hidden="true"></span><span><strong>Conector</strong><small>Iniciando…</small></span></li>
|
|
<li id="checkProject" data-state="waiting"><span class="check-dot" aria-hidden="true"></span><span><strong>Projeto</strong><small>Verificando…</small></span></li>
|
|
<li id="checkSequence" data-state="waiting"><span class="check-dot" aria-hidden="true"></span><span><strong>Sequência ativa</strong><small>Verificando…</small></span></li>
|
|
</ul>
|
|
<p class="field-help">Este painel verifica só o Premiere. No seu assistente de IA, rode <strong>Verify Premiere connection</strong> para a checagem completa.</p>
|
|
</section>
|
|
|
|
<section class="card update-card" aria-live="polite">
|
|
<div class="card-head">
|
|
<h2>Atualizações <span class="card-kicker">MCP updates</span></h2>
|
|
</div>
|
|
<div class="update-body">
|
|
<div class="update-copy">
|
|
<strong id="updateTitle">Version 1.14.9</strong>
|
|
<span id="updateDetail">Verificando o servidor MCP global e a versão do conector…</span>
|
|
</div>
|
|
<button id="btnUpdate" class="button button-ghost" onclick="handleUpdateClick()" type="button" aria-describedby="updateDetail" disabled>
|
|
Verificar
|
|
</button>
|
|
</div>
|
|
</section>
|
|
|
|
<details class="advanced">
|
|
<summary>Pasta de troca de mensagens</summary>
|
|
<div class="advanced-body">
|
|
<label class="field-label" for="tempDir">Pasta local usada pela ponte</label>
|
|
<div class="path-field">
|
|
<svg viewBox="0 0 20 20" aria-hidden="true"><path d="M2.5 5.5h5l1.5 2h8.5v8h-15v-10Z"/></svg>
|
|
<input type="text" id="tempDir" value="" spellcheck="false" autocomplete="off" aria-describedby="tempDirHelp">
|
|
</div>
|
|
<p class="field-help" id="tempDirHelp">Comandos e respostas são trocados por arquivos nesta pasta. Só mude se souber o que está fazendo.</p>
|
|
<button class="button button-ghost" id="btnSave" onclick="saveTempDir()" type="button">Salvar pasta</button>
|
|
</div>
|
|
</details>
|
|
|
|
<details class="log-details">
|
|
<summary>Detalhes técnicos <span class="log-hint">(atividade da ponte)</span></summary>
|
|
<div id="log" role="log" aria-live="polite" aria-relevant="additions" tabindex="0" aria-label="Log da ponte"></div>
|
|
</details>
|
|
|
|
</div>
|
|
|
|
<!-- =============================== MODELOS =============================== -->
|
|
<div class="tab-panel" id="tabModels" role="tabpanel" aria-labelledby="tabBtnModels" hidden>
|
|
|
|
<section class="card">
|
|
<div class="card-head"><h2>Idioma da transcrição</h2></div>
|
|
<label class="field-label" for="transcribeLanguage">Idioma falado nos vídeos</label>
|
|
<select id="transcribeLanguage" class="select-field" onchange="saveLanguage()"></select>
|
|
<p class="field-help">Salvo automaticamente e usado em toda transcrição nova.</p>
|
|
</section>
|
|
|
|
<section class="card">
|
|
<div class="card-head">
|
|
<h2>API Groq</h2>
|
|
<div class="hf-token-status" id="groqApiKeyStatus" data-state="unset">
|
|
<span class="check-dot" aria-hidden="true"></span>
|
|
<span id="groqApiKeyStatusText">Não configurada</span>
|
|
</div>
|
|
</div>
|
|
<label class="field-label" for="groqApiKey">API key</label>
|
|
<div class="path-field">
|
|
<input type="password" id="groqApiKey" placeholder="gsk_..." spellcheck="false" autocomplete="off">
|
|
<button type="button" class="path-field-toggle" onclick="toggleGroqApiKeyVisibility()" aria-label="Mostrar ou ocultar a chave Groq">👁</button>
|
|
</div>
|
|
<p class="field-help">Será usada para transcrição online quando o provider Groq estiver ativo.</p>
|
|
<button class="button button-primary" id="btnSaveGroqApiKey" onclick="saveGroqApiKey()" type="button">Salvar</button>
|
|
</section>
|
|
|
|
<section class="card">
|
|
<div class="card-head"><h2>Modelos Whisper</h2></div>
|
|
<p class="field-help">Modelos maiores acertam mais e demoram mais. O marcado como padrão é o pré-selecionado na aba Editar vídeo.</p>
|
|
<div class="model-list" id="modelList"></div>
|
|
</section>
|
|
|
|
<details class="advanced">
|
|
<summary>Pasta dos modelos</summary>
|
|
<div class="advanced-body">
|
|
<label class="field-label" for="modelsPath">Onde os modelos ficam salvos</label>
|
|
<div class="path-field">
|
|
<svg viewBox="0 0 20 20" aria-hidden="true"><path d="M2.5 5.5h5l1.5 2h8.5v8h-15v-10Z"/></svg>
|
|
<input type="text" id="modelsPath" spellcheck="false" autocomplete="off" aria-describedby="modelsPathHelp">
|
|
</div>
|
|
<p class="field-help" id="modelsPathHelp">Mudar a pasta não move os modelos já baixados.</p>
|
|
<button class="button button-ghost" id="btnSaveModelsPath" onclick="saveModelsPath()" type="button">Salvar pasta</button>
|
|
</div>
|
|
</details>
|
|
|
|
<details class="log-details">
|
|
<summary>Detalhes técnicos <span class="log-hint">(downloads)</span></summary>
|
|
<div id="modelsLog" role="log" aria-live="polite" aria-relevant="additions" tabindex="0" aria-label="Log de download de modelos"></div>
|
|
</details>
|
|
|
|
</div>
|
|
|
|
<!-- ============================ TESTAR AÇÕES ============================ -->
|
|
<div class="tab-panel" id="tabTestarAcoes" role="tabpanel" aria-labelledby="tabBtnTestarAcoes" hidden>
|
|
<p class="tab-intro">Teste a execução de um plano JSON pela engine Python antes de integrar a ação ao fluxo normal de edição.</p>
|
|
|
|
<section class="card">
|
|
<div class="card-head">
|
|
<h2>Plano de teste</h2>
|
|
<span class="step-chip">Somente corte</span>
|
|
</div>
|
|
<p class="field-help">Cole um JSON com uma ou mais actions <code>kind: "cut"</code>. Os tempos precisam estar na mídia original.</p>
|
|
<textarea id="testarAcoesJson" rows="12" spellcheck="false" aria-label="JSON de ações para teste"></textarea>
|
|
<div class="button-row">
|
|
<button class="button button-ghost" id="btnTestarAcoesExemplo" onclick="testarAcoesCarregarExemplo()" type="button">Carregar exemplo</button>
|
|
<button class="button button-danger" id="btnTestarAcoesExecutar" onclick="testarAcoesExecutar()" type="button">Executar na timeline</button>
|
|
</div>
|
|
<p class="field-help" id="testarAcoesStatus"></p>
|
|
</section>
|
|
|
|
<details class="log-details">
|
|
<summary>Detalhes técnicos <span class="log-hint">(engine Python e MCP)</span></summary>
|
|
<div id="testarAcoesLog" role="log" aria-live="polite" aria-relevant="additions" tabindex="0" aria-label="Log do teste de ações"></div>
|
|
</details>
|
|
</div>
|
|
|
|
<!-- =============================== LEGENDAS =============================== -->
|
|
<div class="tab-panel" id="tabLegendas" role="tabpanel" aria-labelledby="tabBtnLegendas" hidden>
|
|
<p class="tab-intro">Configure como as legendas serão geradas automaticamente a partir da transcrição já existente.</p>
|
|
<section class="card">
|
|
<div class="card-head"><h2>Geração automática</h2><span class="step-chip">Offline</span></div>
|
|
<label class="checkbox-field"><input id="legendasHabilitadas" type="checkbox"><span>Gerar legendas quando o plano for aplicado</span></label>
|
|
<p class="field-help">A transcrição fornece o texto e os tempos. A IA decide o plano editorial; o painel gera as legendas localmente somente nos trechos mantidos.</p>
|
|
</section>
|
|
<section class="card">
|
|
<div class="card-head"><h2>Estilo e sincronização</h2></div>
|
|
<div class="form-grid form-grid-2">
|
|
<div><label class="field-label" for="legendasEstilo">Estilo visual</label><select class="select-field" id="legendasEstilo"><option value="word_top_down">Palavras animadas (MOGRT)</option><option value="subtitle_clean">Legenda limpa</option></select></div>
|
|
<div><label class="field-label" for="legendasFaixa">Faixa de vídeo</label><input class="text-field" id="legendasFaixa" type="number" min="0" step="1" value="3"></div>
|
|
<div><label class="field-label" for="legendasMaxPalavras">Máximo de palavras</label><input class="text-field" id="legendasMaxPalavras" type="number" min="1" max="12" step="1" value="5"></div>
|
|
<div><label class="field-label" for="legendasMaxCaracteres">Máximo de caracteres</label><input class="text-field" id="legendasMaxCaracteres" type="number" min="10" max="80" step="1" value="32"></div>
|
|
</div>
|
|
<label class="field-label" for="legendasMogrtPath">Arquivo MOGRT</label>
|
|
<input class="text-field" id="legendasMogrtPath" type="text" spellcheck="false" placeholder="/caminho/para/legenda.mogrt">
|
|
<p class="field-help">A fonte e a animação pertencem ao próprio template MOGRT.</p>
|
|
<button class="button button-primary" id="btnSalvarLegendas" onclick="salvarConfiguracaoDeLegendas()" type="button">Salvar configuração</button>
|
|
<button class="button button-primary" id="btnGerarLegendas" onclick="gerarLegendasDaTranscricao()" type="button">Gerar legendas da transcrição</button>
|
|
<p class="field-help" id="legendasStatus" aria-live="polite"></p>
|
|
</section>
|
|
<section class="card" id="legendasRevisao">
|
|
<div class="card-head"><h2>Revisão <span id="legendasContagem">(0)</span></h2><span class="step-chip">Editável</span></div>
|
|
<p class="field-help">Clique no texto para corrigir. Exclua blocos antes de inserir na timeline.</p>
|
|
<div id="legendasLista" role="list" aria-label="Lista de legendas"><p class="empty-state">Nenhuma legenda carregada. Gere as legendas a partir da transcrição.</p></div>
|
|
<div class="button-row">
|
|
<button class="button button-ghost" onclick="exportarLegendasSrt()" type="button">↓ Exportar SRT</button>
|
|
<button class="button button-primary" onclick="inserirLegendasNaTimeline()" type="button">↓ Inserir na timeline</button>
|
|
</div>
|
|
</section>
|
|
</div>
|
|
|
|
<!-- =============================== AJUSTES =============================== -->
|
|
<div class="tab-panel" id="tabSettings" role="tabpanel" aria-labelledby="tabBtnSettings" hidden>
|
|
|
|
<section class="card">
|
|
<div class="card-head">
|
|
<h2>Token do Hugging Face</h2>
|
|
<div class="hf-token-status" id="hfTokenStatus" data-state="unset">
|
|
<span class="check-dot" aria-hidden="true"></span>
|
|
<span id="hfTokenStatusText">Não configurado</span>
|
|
</div>
|
|
</div>
|
|
<label class="field-label" for="hfToken">Access token</label>
|
|
<div class="path-field">
|
|
<input type="password" id="hfToken" placeholder="hf_..." spellcheck="false" autocomplete="off" aria-describedby="hfTokenHelp">
|
|
<button type="button" class="path-field-toggle" id="btnToggleHfToken" onclick="toggleHfTokenVisibility()" aria-label="Mostrar ou ocultar o token">👁</button>
|
|
</div>
|
|
<p class="field-help" id="hfTokenHelp">Autentica os downloads de modelos e libera a detecção de locutores.</p>
|
|
<div class="button-row">
|
|
<button class="button button-primary" id="btnSaveHfToken" onclick="saveHfToken()" type="button">Salvar</button>
|
|
<button class="button button-ghost" id="btnValidateHfToken" onclick="validateHfToken()" type="button">Validar</button>
|
|
</div>
|
|
</section>
|
|
|
|
<section class="card">
|
|
<div class="card-head">
|
|
<h2>Personalidades de edição</h2>
|
|
<button class="link-button" id="btnNewPersonality" onclick="startNewEditorPersonality()" type="button">+ Nova</button>
|
|
</div>
|
|
<p class="field-help">
|
|
Descreva em texto livre como a edição deve ser feita — ex.: “depoimento”: zoom suave nos momentos
|
|
emotivos, corta hesitação; “podcast”: ritmo ágil, corta silêncio agressivo. A personalidade ativa
|
|
viaja junto no JSON gerado na transcrição.
|
|
</p>
|
|
<div class="model-list" id="editorPersonalityList"></div>
|
|
|
|
<div id="editorPersonalityForm" class="inline-form" hidden>
|
|
<label class="field-label" for="editorPersonalityName">Nome</label>
|
|
<div class="path-field">
|
|
<input type="text" id="editorPersonalityName" placeholder="Ex: Depoimento emocional" spellcheck="false" autocomplete="off">
|
|
</div>
|
|
<label class="field-label" for="editorPersonalityText">Comportamento esperado</label>
|
|
<textarea id="editorPersonalityText" rows="6" placeholder="Descreva livremente como o editor deve tratar esse tipo de vídeo…"></textarea>
|
|
<div class="button-row">
|
|
<button id="btnSavePersonality" class="button button-primary" onclick="saveEditorPersonalityForm()" type="button">Salvar</button>
|
|
<button id="btnCancelPersonality" class="button button-ghost" onclick="cancelEditorPersonalityForm()" type="button">Cancelar</button>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
</div>
|
|
</div>
|
|
|
|
<script charset="utf-8" src="CSInterface.js"></script>
|
|
<script charset="utf-8" src="updater.cjs"></script>
|
|
<script charset="utf-8" src="abas/projeto/projeto.js"></script>
|
|
<script charset="utf-8" src="main.js"></script>
|
|
</body>
|
|
</html>
|