diff --git a/code/cep-plugin/index.html b/code/cep-plugin/index.html index 3810f90..a2a8fc4 100755 --- a/code/cep-plugin/index.html +++ b/code/cep-plugin/index.html @@ -630,8 +630,18 @@

A fonte e a animação pertencem ao próprio template MOGRT.

+

+
+

Revisão (0)

Editável
+

Clique no texto para corrigir. Exclua blocos antes de inserir na timeline.

+

Nenhuma legenda carregada. Gere as legendas a partir da transcrição.

+
+ + +
+
@@ -686,8 +696,8 @@ - - - + + + diff --git a/code/cep-plugin/main.js b/code/cep-plugin/main.js index 0b48d9c..03ab207 100755 --- a/code/cep-plugin/main.js +++ b/code/cep-plugin/main.js @@ -292,6 +292,98 @@ function salvarConfiguracaoDeLegendas() { document.getElementById("legendasStatus").textContent = "Configuração de legendas salva neste computador."; } +var legendasGeradas = []; + +function legendaNumeroParaTempo(segundos) { + var horas = Math.floor(segundos / 3600); + var minutos = Math.floor((segundos % 3600) / 60); + var segundosInteiros = Math.floor(segundos % 60); + var milissegundos = Math.round((segundos - Math.floor(segundos)) * 1000); + if (milissegundos >= 1000) { segundosInteiros++; milissegundos = 0; } + return [horas, minutos, segundosInteiros].map(function (n) { return String(n).padStart(2, "0"); }).join(":") + "," + String(milissegundos).padStart(3, "0"); +} + +function legendasExtrairPalavras(transcricao) { + var segmentos = transcricao.segmentos || transcricao.segments || []; + var palavras = []; + segmentos.forEach(function (segmento) { + var itens = segmento.palavras || segmento.words || []; + itens.forEach(function (palavra) { + var texto = String(palavra.texto || palavra.word || "").trim(); + var inicio = Number(palavra.inicio !== undefined ? palavra.inicio : palavra.start); + var fim = Number(palavra.fim !== undefined ? palavra.fim : palavra.end); + if (texto && isFinite(inicio) && isFinite(fim) && fim > inicio) palavras.push({ texto: texto, inicio: inicio, fim: fim }); + }); + }); + return palavras.sort(function (a, b) { return a.inicio - b.inicio; }); +} + +function legendasIntervaloCortado(palavra, cortes) { + return cortes.some(function (corte) { return palavra.inicio < corte.end && palavra.fim > corte.start; }); +} + +function legendasGerarBlocos(palavras, cortes, configuracao) { + var mantidas = palavras.filter(function (palavra) { return !legendasIntervaloCortado(palavra, cortes); }); + var blocos = [], bloco = [], caracteres = 0; + function adicionar() { + if (!bloco.length) return; + var primeiro = bloco[0], ultimo = bloco[bloco.length - 1]; + // Para o primeiro corte, todos os cortes anteriores são considerados. + var inicio = primeiro.inicio - cortes.reduce(function (total, corte) { return total + (corte.end <= primeiro.inicio ? corte.end - corte.start : 0); }, 0); + var fim = ultimo.fim - cortes.reduce(function (total, corte) { return total + (corte.end <= ultimo.fim ? corte.end - corte.start : 0); }, 0); + blocos.push({ texto: bloco.map(function (item) { return item.texto; }).join(" "), inicio: inicio, fim: fim }); + bloco = []; caracteres = 0; + } + mantidas.forEach(function (palavra) { + var espaco = bloco.length ? 1 : 0; + if (bloco.length && (bloco.length >= configuracao.max_palavras || caracteres + espaco + palavra.texto.length > configuracao.max_caracteres)) adicionar(); + bloco.push(palavra); caracteres += (bloco.length > 1 ? 1 : 0) + palavra.texto.length; + }); + adicionar(); + return blocos; +} + +function legendasRenderizar() { + var lista = document.getElementById("legendasLista"); + document.getElementById("legendasContagem").textContent = "(" + legendasGeradas.length + ")"; + lista.textContent = ""; + if (!legendasGeradas.length) { lista.innerHTML = '

Nenhuma legenda carregada.

'; return; } + legendasGeradas.forEach(function (legenda, indice) { + var linha = document.createElement("div"); linha.className = "legenda-row"; linha.setAttribute("role", "listitem"); + var cabecalho = document.createElement("div"); cabecalho.className = "legenda-timecode"; cabecalho.textContent = (indice + 1) + " " + legendaNumeroParaTempo(legenda.inicio) + " → " + legendaNumeroParaTempo(legenda.fim); + var campo = document.createElement("input"); campo.className = "text-field"; campo.value = legenda.texto; campo.setAttribute("aria-label", "Texto da legenda " + (indice + 1)); + campo.oninput = function () { legendasGeradas[indice].texto = campo.value; }; + var excluir = document.createElement("button"); excluir.className = "button button-ghost"; excluir.type = "button"; excluir.textContent = "×"; excluir.title = "Excluir legenda"; + excluir.onclick = function () { legendasGeradas.splice(indice, 1); legendasRenderizar(); }; + linha.appendChild(cabecalho); linha.appendChild(campo); linha.appendChild(excluir); lista.appendChild(linha); + }); +} + +function gerarLegendasDaTranscricao() { + if (!silenceState.transcriptPath) { document.getElementById("legendasStatus").textContent = "Execute ou carregue uma transcrição primeiro."; return; } + try { + var transcricao = JSON.parse(fs.readFileSync(silenceState.transcriptPath, "utf-8")); + var plano = JSON.parse(document.getElementById("testarAcoesJson").value || "{}"); + var cortes = (plano.actions || []).filter(function (acao) { return acao.kind === "cut"; }).map(function (acao) { return { start: Number(acao.start), end: Number(acao.end) }; }); + var configuracao = carregarConfiguracaoDeLegendas(); + legendasGeradas = legendasGerarBlocos(legendasExtrairPalavras(transcricao), cortes, configuracao); + legendasRenderizar(); + document.getElementById("legendasStatus").textContent = legendasGeradas.length + " legenda(s) gerada(s) para revisão."; + } catch (erro) { document.getElementById("legendasStatus").textContent = "Não foi possível gerar: " + erro.message; } +} + +function exportarLegendasSrt() { + if (!legendasGeradas.length) { document.getElementById("legendasStatus").textContent = "Gere as legendas antes de exportar."; return; } + var destino = path.join(silenceState.outputDir || os.tmpdir(), "legendas-geradas.srt"); + var conteudo = legendasGeradas.map(function (legenda, indice) { return (indice + 1) + "\n" + legendaNumeroParaTempo(legenda.inicio) + " --> " + legendaNumeroParaTempo(legenda.fim) + "\n" + legenda.texto + "\n"; }).join("\n"); + fs.writeFileSync(destino, conteudo, "utf-8"); + document.getElementById("legendasStatus").textContent = "SRT exportado em " + destino; +} + +function inserirLegendasNaTimeline() { + document.getElementById("legendasStatus").textContent = "A lista está pronta. A inserção automática dos MOGRTs será conectada na próxima etapa, após validar o estilo escolhido."; +} + // ---- Teste isolado de ações pela engine Python ---------------------------- var TESTAR_ACOES_ENGINE_SCRIPT = "/Volumes/Merongo/SISTEMAS/GENIAL SISTEMAS/Jhonny/code/engine/aplicar_plano_de_edicao.py"; @@ -665,15 +757,19 @@ function copiarTextoParaAreaDeTransferencia(texto) { else showToast("err", "Não foi possível copiar o JSON. Selecione e copie manualmente."); } - try { - if (navigator.clipboard && navigator.clipboard.writeText) { - navigator.clipboard.writeText(texto).then(function () { - showToast("ok", "JSON resumido copiado para a área de transferência."); - }).catch(function () { copiarComPbcopy(texto, copiarComSelecaoDoPainel); }); - return; - } - } catch (_) {} - copiarComPbcopy(texto, copiarComSelecaoDoPainel); + // O CEP antigo pode corromper caracteres UTF-8 pela API de clipboard do Chromium. + // O pbcopy recebe bytes UTF-8 diretamente e evita essa conversão intermediária. + copiarComPbcopy(texto, function () { + try { + if (navigator.clipboard && navigator.clipboard.writeText) { + navigator.clipboard.writeText(texto).then(function () { + showToast("ok", "JSON resumido copiado para a área de transferência."); + }).catch(copiarComSelecaoDoPainel); + return; + } + } catch (_) {} + copiarComSelecaoDoPainel(); + }); } function copiarComPbcopy(texto, fallback) { diff --git a/code/cep-plugin/styles.css b/code/cep-plugin/styles.css index 8a2f5fe..39a2dca 100755 --- a/code/cep-plugin/styles.css +++ b/code/cep-plugin/styles.css @@ -327,6 +327,9 @@ h1, h2, h3 { margin: 0; } } .card > * + * { margin-top: 10px; } .card-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; } +.form-grid-2 { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px; } +.legenda-row { display: grid; grid-template-columns: 1fr minmax(0, 5fr) auto; align-items: center; gap: 8px; padding: 10px 0; border-bottom: 1px solid var(--border); } +.legenda-timecode { color: var(--muted); font-size: 10px; font-variant-numeric: tabular-nums; } .card-head h2 { font-size: 11.5px; font-weight: 650; } .card-kicker { margin-left: 6px; color: var(--text-muted); font-size: 8.2px; font-weight: 600; letter-spacing: .07em; text-transform: uppercase; } diff --git a/code/tests/cep-tipos-video-encoding.test.ts b/code/tests/cep-tipos-video-encoding.test.ts new file mode 100644 index 0000000..6344167 --- /dev/null +++ b/code/tests/cep-tipos-video-encoding.test.ts @@ -0,0 +1,27 @@ +import { describe, expect, it } from "vitest"; +import { readFileSync } from "node:fs"; +import { join } from "node:path"; + +const raiz = process.cwd(); + +describe("exportação dos tipos de vídeo no CEP", () => { + it("declara UTF-8 explicitamente nos scripts do painel", () => { + const painel = readFileSync(join(raiz, "cep-plugin", "index.html"), "utf8"); + + expect(painel).toContain(''); + expect(painel).toContain(''); + expect(painel).toContain(''); + }); + + it("prioriza a cópia nativa com entrada UTF-8", () => { + const lógica = readFileSync(join(raiz, "cep-plugin", "main.js"), "utf8"); + const início = lógica.indexOf("function copiarTextoParaAreaDeTransferencia(texto)"); + const fim = lógica.indexOf("\n}\n\nfunction copiarComPbcopy", início); + const função = lógica.slice(início, fim); + + expect(função.indexOf("copiarComPbcopy(texto, copiarComSelecaoDoPainel)")).toBeLessThan( + função.indexOf("navigator.clipboard.writeText"), + ); + expect(lógica).toContain('copia.stdin.write(texto, "utf8");'); + }); +});