primocode 10.12.0 → 10.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (207) hide show
  1. package/README.md +74 -12
  2. package/bin/primocode.js +148 -8
  3. package/lib/act.js +48 -1
  4. package/lib/catalogo.js +12 -1
  5. package/lib/config.js +1 -0
  6. package/lib/estudio/assinaturas/gerativo.json +1 -1
  7. package/lib/estudio/camadas.js +4 -0
  8. package/lib/estudio/cota.js +150 -0
  9. package/lib/estudio/dados/projetos/clube-ad5.json +105 -0
  10. package/lib/estudio/dados/projetos/clube-ae-sem-memoria.json +107 -0
  11. package/lib/estudio/dados/projetos/clube-agent.json +120 -0
  12. package/lib/estudio/dados/projetos/clube-antes-e-agora.json +86 -0
  13. package/lib/estudio/dados/projetos/clube-apresentando-clube.json +110 -0
  14. package/lib/estudio/dados/projetos/clube-bluevisa.json +106 -0
  15. package/lib/estudio/dados/projetos/clube-caught-in-the-fire.json +127 -0
  16. package/lib/estudio/dados/projetos/clube-chat-cliente-satisfeito.json +98 -0
  17. package/lib/estudio/dados/projetos/clube-chatgpt-anuncios.json +120 -0
  18. package/lib/estudio/dados/projetos/clube-claude-dj.json +140 -0
  19. package/lib/estudio/dados/projetos/clube-claude-mcp.json +142 -0
  20. package/lib/estudio/dados/projetos/clube-cleanui.json +106 -0
  21. package/lib/estudio/dados/projetos/clube-clinica-take-unico.json +128 -0
  22. package/lib/estudio/dados/projetos/clube-clinica.json +104 -0
  23. package/lib/estudio/dados/projetos/clube-clube-opener.json +125 -0
  24. package/lib/estudio/dados/projetos/clube-clube.json +108 -0
  25. package/lib/estudio/dados/projetos/clube-codigo.json +101 -0
  26. package/lib/estudio/dados/projetos/clube-distilbook.json +104 -0
  27. package/lib/estudio/dados/projetos/clube-drive-upload.json +115 -0
  28. package/lib/estudio/dados/projetos/clube-dub.json +119 -0
  29. package/lib/estudio/dados/projetos/clube-enxovaly.json +105 -0
  30. package/lib/estudio/dados/projetos/clube-fecha7.json +118 -0
  31. package/lib/estudio/dados/projetos/clube-filatov.json +128 -0
  32. package/lib/estudio/dados/projetos/clube-frame.json +108 -0
  33. package/lib/estudio/dados/projetos/clube-glass.json +118 -0
  34. package/lib/estudio/dados/projetos/clube-letra-em-apps.json +84 -0
  35. package/lib/estudio/dados/projetos/clube-letra-globo-3d.json +111 -0
  36. package/lib/estudio/dados/projetos/clube-letra-nos-apps.json +146 -0
  37. package/lib/estudio/dados/projetos/clube-letra-seis-mundos.json +100 -0
  38. package/lib/estudio/dados/projetos/clube-lula-3.json +146 -0
  39. package/lib/estudio/dados/projetos/clube-morph.json +119 -0
  40. package/lib/estudio/dados/projetos/clube-motion-designer.json +115 -0
  41. package/lib/estudio/dados/projetos/clube-motion-pacote.json +112 -0
  42. package/lib/estudio/dados/projetos/clube-movimento.json +103 -0
  43. package/lib/estudio/dados/projetos/clube-not-like-us.json +117 -0
  44. package/lib/estudio/dados/projetos/clube-pinterest-inspiracao.json +86 -0
  45. package/lib/estudio/dados/projetos/clube-pixel-e-tinta.json +108 -0
  46. package/lib/estudio/dados/projetos/clube-ponte.json +118 -0
  47. package/lib/estudio/dados/projetos/clube-processo-edit-tonal.json +126 -0
  48. package/lib/estudio/dados/projetos/clube-processo-motion-100-ia.json +109 -0
  49. package/lib/estudio/dados/projetos/clube-promo.json +114 -0
  50. package/lib/estudio/dados/projetos/clube-proposito.json +115 -0
  51. package/lib/estudio/dados/projetos/clube-rankhog.json +128 -0
  52. package/lib/estudio/dados/projetos/clube-recursao.json +116 -0
  53. package/lib/estudio/dados/projetos/clube-revelar.json +118 -0
  54. package/lib/estudio/dados/projetos/clube-robo.json +125 -0
  55. package/lib/estudio/dados/projetos/clube-shelby-ep2.json +144 -0
  56. package/lib/estudio/dados/projetos/clube-shelby.json +97 -0
  57. package/lib/estudio/dados/projetos/clube-solar-eclipse.json +96 -0
  58. package/lib/estudio/dados/projetos/clube-soldier.json +128 -0
  59. package/lib/estudio/dados/projetos/clube-som-de-cena.json +108 -0
  60. package/lib/estudio/dados/projetos/clube-spotify-candidatura.json +112 -0
  61. package/lib/estudio/dados/projetos/clube-tda-app.json +118 -0
  62. package/lib/estudio/dados/projetos/clube-tda-ia.json +75 -0
  63. package/lib/estudio/dados/projetos/clube-telegram-chat.json +118 -0
  64. package/lib/estudio/dados/projetos/clube-telegram-ilha.json +106 -0
  65. package/lib/estudio/dados/projetos/clube-tennessee-nike.json +132 -0
  66. package/lib/estudio/dados/projetos/clube-tudo-se-move.json +112 -0
  67. package/lib/estudio/dados/projetos/clube-uiad.json +107 -0
  68. package/lib/estudio/dados/projetos/clube-viralgrow.json +108 -0
  69. package/lib/estudio/dados/projetos/clube-whose-side.json +140 -0
  70. package/lib/estudio/dados/projetos/clube-youtube-inscrever.json +108 -0
  71. package/lib/estudio/dados/projetos/elementos-do-site-em-movimento.json +49 -0
  72. package/lib/estudio/dados/projetos/explainer-narrado.json +51 -0
  73. package/lib/estudio/dados/projetos/lancamento-de-produto.json +51 -0
  74. package/lib/estudio/dados/projetos/motion-so-com-sons.json +50 -0
  75. package/lib/estudio/dados/projetos/titulo-em-after-effects.json +51 -0
  76. package/lib/estudio/dados/projetos/tour-de-saas.json +50 -0
  77. package/lib/estudio/dados/projetos-ae/antes-e-agora/ae/cenas.jsx +733 -0
  78. package/lib/estudio/dados/projetos-ae/antes-e-agora/assets/fazer_assets.py +88 -0
  79. package/lib/estudio/dados/projetos-ae/letra-em-apps/ae/cenas.jsx +402 -0
  80. package/lib/estudio/dados/projetos-ae/letra-em-apps/assets/preparar_assets.py +17 -0
  81. package/lib/estudio/dados/projetos-ae/letra-em-apps/assets/svg_para_ae.py +25 -0
  82. package/lib/estudio/dados/projetos-ae/letra-nos-apps/BRIEF-AGENTES.md +31 -0
  83. package/lib/estudio/dados/projetos-ae/letra-nos-apps/README.md +53 -0
  84. package/lib/estudio/dados/projetos-ae/letra-nos-apps/ROTEIRO-CENAS.md +69 -0
  85. package/lib/estudio/dados/projetos-ae/letra-nos-apps/ae/A-agenda.jsx +148 -0
  86. package/lib/estudio/dados/projetos-ae/letra-nos-apps/ae/B-docs.jsx +174 -0
  87. package/lib/estudio/dados/projetos-ae/letra-nos-apps/ae/C-alttab.jsx +113 -0
  88. package/lib/estudio/dados/projetos-ae/letra-nos-apps/ae/D-spotify.jsx +118 -0
  89. package/lib/estudio/dados/projetos-ae/letra-nos-apps/ae/E-notas.jsx +146 -0
  90. package/lib/estudio/dados/projetos-ae/letra-nos-apps/ae/F-jornal1.jsx +144 -0
  91. package/lib/estudio/dados/projetos-ae/letra-nos-apps/ae/G-google.jsx +153 -0
  92. package/lib/estudio/dados/projetos-ae/letra-nos-apps/ae/H-mapa.jsx +176 -0
  93. package/lib/estudio/dados/projetos-ae/letra-nos-apps/ae/I-imagens.jsx +132 -0
  94. package/lib/estudio/dados/projetos-ae/letra-nos-apps/ae/J-jornal2.jsx +191 -0
  95. package/lib/estudio/dados/projetos-ae/letra-nos-apps/ae/K-maps.jsx +184 -0
  96. package/lib/estudio/dados/projetos-ae/letra-nos-apps/ae/camera-movimentos.jsx +48 -0
  97. package/lib/estudio/dados/projetos-ae/letra-nos-apps/ae/camera.jsx +57 -0
  98. package/lib/estudio/dados/projetos-ae/letra-nos-apps/ae/card-spotify.jsx +98 -0
  99. package/lib/estudio/dados/projetos-ae/letra-nos-apps/ae/digitar.jsx +38 -0
  100. package/lib/estudio/dados/projetos-ae/letra-nos-apps/ae/jornal-comum.jsx +196 -0
  101. package/lib/estudio/dados/projetos-ae/letra-nos-apps/ae/letra.jsx +23 -0
  102. package/lib/estudio/dados/projetos-ae/letra-nos-apps/ae/letra_automatica.py +83 -0
  103. package/lib/estudio/dados/projetos-ae/letra-nos-apps/ae/montagem.jsx +74 -0
  104. package/lib/estudio/dados/projetos-ae/letra-nos-apps/ae/w-agenda/render_html.py +13 -0
  105. package/lib/estudio/dados/projetos-ae/letra-nos-apps/ae/w-agenda/util.jsx +162 -0
  106. package/lib/estudio/dados/projetos-ae/letra-nos-apps/ae/w-midia-comum.jsx +114 -0
  107. package/lib/estudio/dados/projetos-ae/letra-nos-apps/ae/wg-util.jsx +360 -0
  108. package/lib/estudio/dados/projetos-ae/letra-nos-apps/assets/F-jornal1/render_html.py +13 -0
  109. package/lib/estudio/dados/projetos-ae/letra-nos-apps/assets/render_w-midia.py +26 -0
  110. package/lib/estudio/dados/projetos-ae/letra-nos-apps/assets/w-midia-ferramentas/inclin.py +30 -0
  111. package/lib/estudio/dados/projetos-ae/letra-nos-apps/assets/w-midia-ferramentas/medir_d.py +36 -0
  112. package/lib/estudio/dados/projetos-ae/letra-nos-apps/assets/w-midia-ferramentas/medir_e.py +27 -0
  113. package/lib/estudio/dados/projetos-ae/letra-nos-apps/camera/ajustar_movimentos.py +61 -0
  114. package/lib/estudio/dados/projetos-ae/letra-nos-apps/camera/ancorar.py +74 -0
  115. package/lib/estudio/dados/projetos-ae/letra-nos-apps/camera/assar.py +47 -0
  116. package/lib/estudio/dados/projetos-ae/letra-nos-apps/camera/camera3d_jornal.py +172 -0
  117. package/lib/estudio/dados/projetos-ae/letra-nos-apps/camera/comparar.py +32 -0
  118. package/lib/estudio/dados/projetos-ae/letra-nos-apps/camera/dados_ae_jornal.py +46 -0
  119. package/lib/estudio/dados/projetos-ae/letra-nos-apps/camera/folha_contato.py +28 -0
  120. package/lib/estudio/dados/projetos-ae/letra-nos-apps/camera/homografia_jornal.py +74 -0
  121. package/lib/estudio/dados/projetos-ae/letra-nos-apps/camera/integrar.py +44 -0
  122. package/lib/estudio/dados/projetos-ae/letra-nos-apps/camera/medir_quadro_a_quadro.py +32 -0
  123. package/lib/estudio/dados/projetos-ae/letra-nos-apps/camera/mesa_2d.py +64 -0
  124. package/lib/estudio/dados/projetos-ae/letra-nos-apps/camera/plot_cenas.py +11 -0
  125. package/lib/estudio/dados/projetos-ae/letra-nos-apps/camera/retificar_quadros.py +38 -0
  126. package/lib/estudio/dados/projetos-ae/letra-nos-apps/camera/w-agenda/assar_ab.py +52 -0
  127. package/lib/estudio/dados/projetos-ae/letra-nos-apps/camera/w-agenda/medir_ab.py +59 -0
  128. package/lib/estudio/dados/projetos-ae/letra-nos-apps/camera/wg_ajustar_h.py +111 -0
  129. package/lib/estudio/dados/projetos-ae/letra-nos-apps/camera/wg_corrige_cam.py +17 -0
  130. package/lib/estudio/dados/projetos-ae/letra-nos-apps/camera/wg_ecc_cadeia.py +80 -0
  131. package/lib/estudio/dados/projetos-ae/letra-nos-apps/camera/wg_finalizar_cam.py +65 -0
  132. package/lib/estudio/dados/projetos-ae/letra-nos-apps/camera/wg_mosaico.py +61 -0
  133. package/lib/estudio/dados/projetos-ae/letra-nos-apps/guia-do-metodo/tutorial-lyric-edit-interfaces/01-gramatica-e-roteiro.md +55 -0
  134. package/lib/estudio/dados/projetos-ae/letra-nos-apps/guia-do-metodo/tutorial-lyric-edit-interfaces/02-camera.md +66 -0
  135. package/lib/estudio/dados/projetos-ae/letra-nos-apps/guia-do-metodo/tutorial-lyric-edit-interfaces/03-montagem-no-ae.md +50 -0
  136. package/lib/estudio/dados/projetos-ae/letra-nos-apps/guia-do-metodo/tutorial-lyric-edit-interfaces/04-render-e-conferencia.md +43 -0
  137. package/lib/estudio/dados/projetos-ae/letra-nos-apps/guia-do-metodo/tutorial-lyric-edit-interfaces/05-abertura-celular-mergulho.md +43 -0
  138. package/lib/estudio/dados/projetos-ae/letra-nos-apps/guia-do-metodo/tutorial-lyric-edit-interfaces.md +42 -0
  139. package/lib/estudio/dados/projetos-ae/letra-nos-apps/transicao/atraso_imagem.py +31 -0
  140. package/lib/estudio/dados/projetos-ae/letra-nos-apps/transicao/fundo.py +56 -0
  141. package/lib/estudio/dados/projetos-ae/letra-nos-apps/transicao/juntar.py +45 -0
  142. package/lib/estudio/dados/projetos-ae/letra-nos-apps/transicao/montar.py +177 -0
  143. package/lib/estudio/dados/projetos-ae/letra-nos-apps/transicao/rastreio.py +74 -0
  144. package/lib/estudio/dados/projetos-ae/letra-nos-apps/transicao/sincronia.py +18 -0
  145. package/lib/estudio/dados/projetos-ae/pinterest-inspiracao/ae/cenas.jsx +530 -0
  146. package/lib/estudio/dados/projetos-ae/pinterest-inspiracao/assets/fazer_assets.py +107 -0
  147. package/lib/estudio/dados/projetos-ae/processo-motion-100-ia/assets/pessoas/figurinha.py +46 -0
  148. package/lib/estudio/dados/projetos-ae/processo-motion-100-ia/comp-a/BRIEF-A.md +83 -0
  149. package/lib/estudio/dados/projetos-ae/processo-motion-100-ia/comp-a/audio.py +179 -0
  150. package/lib/estudio/dados/projetos-ae/processo-motion-100-ia/comp-a/confere.py +20 -0
  151. package/lib/estudio/dados/projetos-ae/processo-motion-100-ia/comp-a/dados/tempo-parte1.js +1 -0
  152. package/lib/estudio/dados/projetos-ae/processo-motion-100-ia/comp-a/dados/tempo-parte2.js +1 -0
  153. package/lib/estudio/dados/projetos-ae/processo-motion-100-ia/comp-a/gerar_emojis.py +11 -0
  154. package/lib/estudio/dados/projetos-ae/processo-motion-100-ia/comp-a/img/filmes/_indice.js +122 -0
  155. package/lib/estudio/dados/projetos-ae/processo-motion-100-ia/comp-a/js/base.js +380 -0
  156. package/lib/estudio/dados/projetos-ae/processo-motion-100-ia/comp-a/js/main.js +86 -0
  157. package/lib/estudio/dados/projetos-ae/processo-motion-100-ia/comp-a/js/p1a.js +169 -0
  158. package/lib/estudio/dados/projetos-ae/processo-motion-100-ia/comp-a/js/p1b.js +455 -0
  159. package/lib/estudio/dados/projetos-ae/processo-motion-100-ia/comp-a/js/p1c.js +389 -0
  160. package/lib/estudio/dados/projetos-ae/processo-motion-100-ia/comp-a/js/p2.js +379 -0
  161. package/lib/estudio/dados/projetos-ae/processo-motion-100-ia/comp-a/video.py +62 -0
  162. package/lib/estudio/dados/projetos-ae/processo-motion-100-ia/take-unico/README.md +65 -0
  163. package/lib/estudio/dados/projetos-ae/processo-motion-100-ia/take-unico/oraculo_take.py +137 -0
  164. package/lib/estudio/dados/projetos-ae/processo-motion-100-ia/take-unico/render_take.py +388 -0
  165. package/lib/estudio/dados/projetos-ae/processo-motion-100-ia/take-unico/take.js +235 -0
  166. package/lib/estudio/diretor.js +2 -1
  167. package/lib/estudio/editor.js +285 -0
  168. package/lib/estudio/efeitos-fabrica/terminal-digitando.jsx +2 -1
  169. package/lib/estudio/effectcraft.js +152 -0
  170. package/lib/estudio/exportar.js +181 -0
  171. package/lib/estudio/gerativo.js +44 -15
  172. package/lib/estudio/index.js +126 -5
  173. package/lib/estudio/kit/gerativo.jsx +15 -0
  174. package/lib/estudio/kit-ae/base.jsx +1146 -0
  175. package/lib/estudio/kit-ae/cenas.jsx +395 -0
  176. package/lib/estudio/kit-ae/primitivas.jsx +911 -0
  177. package/lib/estudio/kokoro.js +3 -1
  178. package/lib/estudio/maquina-diretor.js +393 -0
  179. package/lib/estudio/maquina-elementos.js +134 -0
  180. package/lib/estudio/maquina-layout.js +198 -0
  181. package/lib/estudio/maquina-som.js +478 -0
  182. package/lib/estudio/maquina-trilha.js +160 -0
  183. package/lib/estudio/maquina-voz.js +429 -0
  184. package/lib/estudio/maquina.js +516 -0
  185. package/lib/estudio/projetos.js +60 -0
  186. package/lib/estudio/remotion.js +6 -4
  187. package/lib/estudio/revisor.js +61 -0
  188. package/lib/estudio/voicestudio.js +81 -0
  189. package/lib/headless.js +71 -0
  190. package/lib/hooks.js +72 -0
  191. package/lib/local/cerebro.json +398 -12
  192. package/lib/local/entrada.js +46 -2
  193. package/lib/marca-kit.js +309 -0
  194. package/lib/mcp.js +286 -6
  195. package/lib/perigo.js +31 -3
  196. package/lib/planos.js +47 -19
  197. package/lib/preparar.js +111 -0
  198. package/lib/retomar.js +55 -0
  199. package/lib/sandbox.js +197 -0
  200. package/lib/skills.js +3 -1
  201. package/lib/tools.js +13 -4
  202. package/lib/ui.js +1 -1
  203. package/lib/video/auto.js +18 -3
  204. package/package.json +4 -2
  205. package/skills/after-effects/SKILL.md +52 -0
  206. package/skills/maquina-de-motion/SKILL.md +84 -0
  207. package/skills/motion-de-primeira/SKILL.md +33 -51
@@ -0,0 +1,198 @@
1
+ /**
2
+ * maquina-layout.js — a conferência de layout DEPOIS do render (o JSX já mede com sourceRectAtTime e encaixa o
3
+ * texto na área segura; aqui o Node olha os quadros de verdade).
4
+ *
5
+ * folhaDeContato(video, saida, dur) → PNG com 8 quadros (ffmpeg fps=8/DUR, scale=270, tile=4x2)
6
+ * medirQuadro(png) → {variancia, contraste, borda}
7
+ * conferir(quadros) → {vazios, baixoContraste, bordas, ok}
8
+ * avisosPorCena(avisos) → {cena → [texto]}: lê "cena N: ..." das linhas AVISO do kit
9
+ * corrigir(plano, resultado) → o plano com o corpo menor nas cenas que estouraram, ou a cena trocada
10
+ */
11
+
12
+ 'use strict';
13
+
14
+ const fs = require('fs');
15
+ const { spawnSync } = require('child_process');
16
+
17
+ const LIMIAR_VAZIO = 12; // variância de luminância abaixo disso = tela lisa
18
+ const LIMIAR_CONTRASTE = 4.5; // WCAG de texto normal, medido entre o "papel" e a "tinta" do quadro
19
+ const LIMIAR_BORDA = 0.16; // fração da faixa da borda com conteúdo forte
20
+
21
+ /** Os pixels (rgb24) do quadro numa largura pequena; null se o ffmpeg falhar. */
22
+ function pixels(png, largura = 108) {
23
+ const r = spawnSync('ffmpeg', ['-v', 'error', '-i', png, '-vf', `scale=${largura}:-2`, '-pix_fmt', 'rgb24', '-f', 'rawvideo', '-'], { maxBuffer: 16 * 1024 * 1024 });
24
+ const info = spawnSync('ffprobe', ['-v', 'error', '-select_streams', 'v:0', '-show_entries', 'stream=width,height', '-of', 'csv=p=0', png], { encoding: 'utf8' });
25
+ const [w, h] = String(info.stdout || '').trim().split(',').map(Number);
26
+ if (r.status !== 0 || !r.stdout || !r.stdout.length || !w || !h) return null;
27
+ const W = largura, H = Math.round(r.stdout.length / 3 / W);
28
+ return { buf: r.stdout, W, H };
29
+ }
30
+ const lin = (v) => { v /= 255; return v <= 0.03928 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4); };
31
+ const lumi = (buf, i) => 0.2126 * lin(buf[i]) + 0.7152 * lin(buf[i + 1]) + 0.0722 * lin(buf[i + 2]);
32
+
33
+ /** Puro: as medidas de um quadro a partir dos pixels. */
34
+ function analisarPixels({ buf, W, H }) {
35
+ const n = W * H, L = new Float32Array(n);
36
+ let soma = 0;
37
+ for (let i = 0; i < n; i++) { L[i] = lumi(buf, i * 3); soma += L[i]; }
38
+ const med = soma / n;
39
+ let v = 0; for (let i = 0; i < n; i++) v += (L[i] * 255 - med * 255) * (L[i] * 255 - med * 255);
40
+ const variancia = v / n;
41
+ const ord = Array.from(L).sort((a, b) => a - b);
42
+ // o texto ocupa pouca área: a tinta é o extremo (0,3% de cada ponta), o papel é a mediana
43
+ const p02 = ord[Math.floor(n * 0.003)], p98 = ord[Math.min(n - 1, Math.floor(n * 0.997))], mediana = ord[Math.floor(n / 2)];
44
+ // papel = o valor mais comum (mediana); tinta = o percentil mais distante dele
45
+ const tinta = Math.abs(p98 - mediana) >= Math.abs(p02 - mediana) ? p98 : p02;
46
+ const contraste = (Math.max(mediana, tinta) + 0.05) / (Math.min(mediana, tinta) + 0.05);
47
+ // a faixa da borda (3% de cada lado, tirando o meio-alto para ignorar o brilho de fundo): conteúdo forte?
48
+ const faixa = Math.max(1, Math.round(W * 0.03)), forte = [];
49
+ let total = 0, fortes = 0;
50
+ for (let y = Math.round(H * 0.08); y < H * 0.88; y++) {
51
+ for (let x = 0; x < faixa; x++) for (const xx of [x, W - 1 - x]) { total++; if (Math.abs(L[y * W + xx] - mediana) > 0.35) fortes++; }
52
+ }
53
+ return { variancia: Math.round(variancia * 10) / 10, contraste: Math.round(contraste * 10) / 10, borda: total ? Math.round(fortes / total * 1000) / 1000 : 0, mediana };
54
+ }
55
+ function medirQuadro(png) {
56
+ const p = pixels(png);
57
+ return p ? analisarPixels(p) : null;
58
+ }
59
+
60
+
61
+ // ── borda da comp, sólidos fora da paleta e borrão ───────────────────────
62
+
63
+ const hexRgb = (h) => { const s = String(h).replace('#', ''); return [0, 2, 4].map((i) => parseInt(s.substr(i, 2), 16)); };
64
+ const dist = (a, b) => Math.max(Math.abs(a[0] - b[0]), Math.abs(a[1] - b[1]), Math.abs(a[2] - b[2]));
65
+
66
+ /** Uma faixa uniforme e diferente do resto na borda esquerda/direita/topo/base (o fim do fundo aparecendo). Devolve {lado, delta} ou null. */
67
+ function faixaDeBorda({ buf, W, H }) {
68
+ const px = (x, y) => { const i = (y * W + x) * 3; return [buf[i], buf[i + 1], buf[i + 2]]; };
69
+ const media = (x0, x1, y0, y1) => { let r = 0, g = 0, b = 0, n = 0, mn = [255, 255, 255], mx = [0, 0, 0]; for (let y = y0; y < y1; y++) for (let x = x0; x < x1; x++) { const p = px(x, y); r += p[0]; g += p[1]; b += p[2]; n++; for (let k = 0; k < 3; k++) { mn[k] = Math.min(mn[k], p[k]); mx[k] = Math.max(mx[k], p[k]); } } return { c: [r / n, g / n, b / n], amp: Math.max(mx[0] - mn[0], mx[1] - mn[1], mx[2] - mn[2]) }; };
70
+ const e = Math.max(2, Math.round(W * 0.025)), y0 = Math.round(H * 0.1), y1 = Math.round(H * 0.9), x0 = Math.round(W * 0.1), x1 = Math.round(W * 0.9);
71
+ const lados = [['esquerda', media(0, e, y0, y1), media(e + 2, e * 2 + 2, y0, y1)], ['direita', media(W - e, W, y0, y1), media(W - e * 2 - 2, W - e - 2, y0, y1)], ['topo', media(x0, x1, 0, e), media(x0, x1, e + 2, e * 2 + 2)], ['base', media(x0, x1, H - e, H), media(x0, x1, H - e * 2 - 2, H - e - 2)]];
72
+ for (const [lado, borda, dentro] of lados) {
73
+ const d = dist(borda.c, dentro.c);
74
+ if (d > 38 && borda.amp < 26) return { lado, delta: Math.round(d) }; // uniforme e bem diferente do que vem logo depois
75
+ }
76
+ return null;
77
+ }
78
+
79
+ /** Fração do quadro coberta por células sólidas PRETAS ou BRANCAS que não são cor da paleta (retângulo solto, painel torto sem preenchimento certo). */
80
+ function solidosForaDaPaleta({ buf, W, H }, paleta) {
81
+ const pal = (paleta || []).map(hexRgb);
82
+ const cw = 6, ch = 6, nx = Math.floor(W / cw), ny = Math.floor(H / ch);
83
+ let cel = 0;
84
+ for (let j = 0; j < ny; j++) for (let i = 0; i < nx; i++) {
85
+ let r = 0, g = 0, b = 0, mn = 255, mx = 0;
86
+ for (let y = j * ch; y < (j + 1) * ch; y++) for (let x = i * cw; x < (i + 1) * cw; x++) { const k = (y * W + x) * 3; r += buf[k]; g += buf[k + 1]; b += buf[k + 2]; const m = Math.max(buf[k], buf[k + 1], buf[k + 2]), n = Math.min(buf[k], buf[k + 1], buf[k + 2]); mx = Math.max(mx, m); mn = Math.min(mn, n); }
87
+ if (mx - mn > 14) continue;
88
+ const c = [r / (cw * ch), g / (cw * ch), b / (cw * ch)];
89
+ const preto = Math.max(...c) < 22, branco = Math.min(...c) > 238;
90
+ if ((preto || branco) && !pal.some((p) => dist(p, c) < 26)) cel++;
91
+ }
92
+ return cel / (nx * ny);
93
+ }
94
+
95
+ /** A nitidez do quadro: variância do Laplaciano em cinza (borrão ilegível = valor muito abaixo do dos quadros de texto da mesma cena). */
96
+ function nitidez({ buf, W, H }) {
97
+ const g = new Float32Array(W * H);
98
+ for (let i = 0; i < W * H; i++) g[i] = 0.299 * buf[i * 3] + 0.587 * buf[i * 3 + 1] + 0.114 * buf[i * 3 + 2];
99
+ let s = 0, s2 = 0, n = 0;
100
+ for (let y = 1; y < H - 1; y++) for (let x = 1; x < W - 1; x++) { const v = 4 * g[y * W + x] - g[y * W + x - 1] - g[y * W + x + 1] - g[(y - 1) * W + x] - g[(y + 1) * W + x]; s += v; s2 += v * v; n++; }
101
+ const m = s / n; return Math.round((s2 / n - m * m) * 10) / 10;
102
+ }
103
+ /** A variância de luminância (há conteúdo no quadro, ou é um quadro liso?). */
104
+ function varLum({ buf, W, H }) { let s = 0, s2 = 0; const n = W * H; for (let i = 0; i < n; i++) { const v = 0.299 * buf[i * 3] + 0.587 * buf[i * 3 + 1] + 0.114 * buf[i * 3 + 2]; s += v; s2 += v * v; } const m = s / n; return s2 / n - m * m; }
105
+ const lerPixels = (png, W = 135) => pixels(png, W);
106
+
107
+ /** Confere cada quadro (um por cena). */
108
+ function conferir(quadros) {
109
+ const vazios = [], baixoContraste = [], bordas = [], medidas = [];
110
+ (quadros || []).forEach((q, i) => {
111
+ const m = medirQuadro(q);
112
+ medidas.push(m);
113
+ if (!m) return;
114
+ if (m.variancia < LIMIAR_VAZIO) vazios.push({ cena: i, variancia: m.variancia });
115
+ else if (m.contraste < LIMIAR_CONTRASTE) baixoContraste.push({ cena: i, contraste: m.contraste });
116
+ if (m.borda > LIMIAR_BORDA) bordas.push({ cena: i, borda: m.borda });
117
+ });
118
+ return { vazios, baixoContraste, bordas, medidas, ok: !vazios.length && !baixoContraste.length && !bordas.length };
119
+ }
120
+
121
+ /** "cena 3: texto largo demais: x" → {3: ['texto largo demais: x']}; aviso sem cena vai em `geral`. */
122
+ function avisosPorCena(avisos) {
123
+ const por = {};
124
+ for (const a of avisos || []) {
125
+ const m = String(a).match(/^cena (\d+): (.*)$/);
126
+ const k = m ? Number(m[1]) : 'geral';
127
+ (por[k] = por[k] || []).push(m ? m[2] : a);
128
+ }
129
+ return por;
130
+ }
131
+
132
+ /**
133
+ * O plano corrigido para a próxima tentativa: nas cenas com texto fora/sobreposto o corpo encolhe 15% (acumula); nas
134
+ * de quadro vazio, a segunda falha troca a primitiva por uma segura; nas de contraste baixo, a paleta passa para a
135
+ * variação de fundo. `r` = {avisos, vazios, baixoContraste, bordas}. Devolve a lista do que mudou.
136
+ */
137
+ function corrigir(plano, r, { tentativa = 1, paletaFundo = null } = {}) {
138
+ const mudou = [];
139
+ const por = avisosPorCena(r.avisos || []);
140
+ for (const k of Object.keys(por)) {
141
+ const c = plano.cenas[Number(k)];
142
+ if (!c) continue;
143
+ const l = por[k];
144
+ const comAmbiente = l.some((x) => /ambiente/.test(x));
145
+ if (comAmbiente && !c.semAmbiente) { c.semAmbiente = true; mudou.push(`cena ${k}: sem o elemento de enfeite (${l.find((x) => /ambiente/.test(x))})`); }
146
+ const outros = l.filter((x) => !/ambiente/.test(x) && !/herói pequeno|texto principal pequeno/.test(x));
147
+ if (outros.length) {
148
+ if ((c.ajuste || 1) <= 0.9 || c.semRotulos === undefined && (c.ajuste || 1) < 1) { // 2ª vez: some o que é opcional (rótulos e enfeite) em vez de encolher o texto até ficar ilegível
149
+ if (!c.semRotulos) { c.semRotulos = true; c.semAmbiente = true; mudou.push(`cena ${k}: sem rótulos opcionais e sem enfeite (${outros[0]})`); }
150
+ else if ((c.ajuste || 1) > 0.76) { c.ajuste = Math.round((c.ajuste || 1) * 0.92 * 100) / 100; mudou.push(`cena ${k}: corpo ${Math.round(c.ajuste * 100)}% (${outros[0]})`); }
151
+ } else {
152
+ c.ajuste = Math.round((c.ajuste || 1) * 0.88 * 100) / 100;
153
+ mudou.push(`cena ${k}: corpo ${Math.round(c.ajuste * 100)}% (${outros[0]})`);
154
+ }
155
+ }
156
+ }
157
+ for (const v of r.vazios || []) {
158
+ const c = plano.cenas[v.cena];
159
+ if (!c) continue;
160
+ if (tentativa >= 2 || c.tipo === 'cinetico') { c.tipo = 'cinetico'; mudou.push(`cena ${v.cena}: quadro vazio, trocada por cinetico`); }
161
+ else { c.ajuste = Math.round((c.ajuste || 1) * 0.9 * 100) / 100; mudou.push(`cena ${v.cena}: quadro vazio, tenta de novo`); }
162
+ }
163
+ for (const b of r.baixoContraste || []) {
164
+ const c = plano.cenas[b.cena];
165
+ if (!c || !paletaFundo || b.contraste >= 3) continue; // entre 3 e 4,5 só avisa: o texto é grande e a paleta já garante o contraste
166
+ c.pal = { ...paletaFundo };
167
+ mudou.push(`cena ${b.cena}: contraste ${b.contraste}, paleta de fundo`);
168
+ }
169
+ for (const b of (r.pixels && r.pixels.borrao) || []) { // troca borrada demais: vira corte seco (sem desfoque)
170
+ if (b.cena >= 1 && plano.trocas && plano.trocas[b.cena - 1] && plano.trocas[b.cena - 1] !== 'corte') { mudou.push(`troca ${b.cena}: borrão (nitidez ${b.nitidez}), ${plano.trocas[b.cena - 1]} vira corte`); plano.trocas[b.cena - 1] = 'corte'; }
171
+ }
172
+ for (const b of (r.pixels && r.pixels.solidoForaDaPaleta) || []) { // retângulo sólido preto/branco: a cena troca para a primitiva limpa
173
+ const c = plano.cenas[b.cena];
174
+ if (c && c.tipo !== 'cinetico' && c.tipo !== 'cta') { mudou.push(`cena ${b.cena}: sólido fora da paleta (${b.fracao}), troca ${c.tipo} por cinetico`); c.tipo = 'cinetico'; c.elemento = false; }
175
+ }
176
+ for (const t of r.tardias || []) {
177
+ const c = plano.cenas[t.cena];
178
+ if (!c) continue;
179
+ c.f = Math.round(Math.min(2.4, Math.max((c.f || 1) * 1.2, (t.entrou * (c.f || 1)) / (0.36 * c.dur))) * 100) / 100;
180
+ mudou.push(`cena ${t.cena}: entrada tardia (${t.entrou} s de ${t.dur} s), acelera ${c.f}×`);
181
+ }
182
+ for (const b of r.bordas || []) {
183
+ const c = plano.cenas[b.cena];
184
+ if (!c) continue;
185
+ c.ajuste = Math.round((c.ajuste || 1) * 0.9 * 100) / 100;
186
+ mudou.push(`cena ${b.cena}: conteúdo colado na borda, corpo ${Math.round(c.ajuste * 100)}%`);
187
+ }
188
+ return mudou;
189
+ }
190
+
191
+ /** A folha de contato: 8 quadros espalhados pela duração, 4×2. */
192
+ function folhaDeContato(video, saida, dur) {
193
+ const d = Math.max(1, Number(dur) || 15);
194
+ const r = spawnSync('ffmpeg', ['-y', '-v', 'error', '-i', video, '-vf', `fps=8/${d.toFixed(3)},scale=270:-1,tile=4x2`, '-frames:v', '1', saida], { encoding: 'utf8' });
195
+ return r.status === 0 && fs.existsSync(saida) ? saida : null;
196
+ }
197
+
198
+ module.exports = { varLum, faixaDeBorda, solidosForaDaPaleta, nitidez, lerPixels, pixels, analisarPixels, medirQuadro, conferir, avisosPorCena, corrigir, folhaDeContato, LIMIAR_VAZIO, LIMIAR_CONTRASTE, LIMIAR_BORDA };