primocode 9.10.0 → 10.0.9

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 (89) hide show
  1. package/README.md +30 -6
  2. package/bin/primocode.js +169 -342
  3. package/lib/act.js +360 -39
  4. package/lib/browser.js +105 -4
  5. package/lib/casar.js +108 -0
  6. package/lib/catalogo.js +6 -2
  7. package/lib/claude-engine.js +38 -17
  8. package/lib/codex-engine.js +27 -9
  9. package/lib/config.js +10 -3
  10. package/lib/cursor.js +10 -1
  11. package/lib/entrada.js +77 -11
  12. package/lib/estudio/cinema.js +27 -9
  13. package/lib/estudio/diretor.js +21 -1
  14. package/lib/estudio/ende.js +16 -5
  15. package/lib/estudio/estilo.js +66 -0
  16. package/lib/estudio/gerativo.js +1453 -0
  17. package/lib/estudio/glifos.js +215 -0
  18. package/lib/estudio/hibrido.js +165 -0
  19. package/lib/estudio/index.js +284 -53
  20. package/lib/estudio/kit/base.jsx +285 -0
  21. package/lib/estudio/kit/gerativo.jsx +980 -0
  22. package/lib/estudio/kit/index.jsx +13 -0
  23. package/lib/estudio/kit/marca.jsx +741 -0
  24. package/lib/estudio/kit/tres.jsx +308 -0
  25. package/lib/estudio/kit/vox.jsx +357 -0
  26. package/lib/estudio/kit.js +78 -0
  27. package/lib/estudio/lancamento.js +441 -0
  28. package/lib/estudio/motion.js +2 -5
  29. package/lib/estudio/plano.js +28 -3
  30. package/lib/estudio/referencias.js +105 -0
  31. package/lib/estudio/remotion.js +139 -51
  32. package/lib/estudio/ritmo.js +77 -0
  33. package/lib/estudio/sons-leves.js +38 -1
  34. package/lib/estudio/variedade.js +105 -0
  35. package/lib/estudio/vox.js +414 -0
  36. package/lib/fala.js +1 -94
  37. package/lib/mcp.js +156 -0
  38. package/lib/personas.js +67 -0
  39. package/lib/pipeline.js +55 -56
  40. package/lib/repeticao.js +3 -1
  41. package/lib/skills.js +44 -2
  42. package/lib/snapshot.js +111 -0
  43. package/lib/tools.js +100 -22
  44. package/lib/ui.js +50 -8
  45. package/lib/video/analise.js +89 -0
  46. package/lib/video/auto.js +329 -0
  47. package/lib/video/camadas.js +207 -0
  48. package/lib/video/cli.js +74 -0
  49. package/lib/video/delta.js +90 -0
  50. package/lib/video/edits.js +379 -0
  51. package/lib/video/filters.js +280 -0
  52. package/lib/video/fx3d/Camadas.jsx +64 -0
  53. package/lib/video/fx3d/Objetos.jsx +128 -0
  54. package/lib/video/fx3d/Root.jsx +18 -0
  55. package/lib/video/fx3d/modelos.jsx +304 -0
  56. package/lib/video/fx3d.js +108 -0
  57. package/lib/video/gerados.js +119 -0
  58. package/lib/video/objetos.js +111 -0
  59. package/lib/video/ops-fx.js +453 -0
  60. package/lib/video/ops.js +347 -0
  61. package/lib/video/paralelo.js +50 -0
  62. package/lib/video/pedido.js +191 -0
  63. package/lib/video/projeto.js +133 -0
  64. package/lib/video/rastrear.js +95 -0
  65. package/lib/video/rastro.js +276 -0
  66. package/lib/video/render.js +274 -0
  67. package/lib/video/texto.js +144 -0
  68. package/lib/video/timeline.js +318 -0
  69. package/lib/video/transcript.js +124 -0
  70. package/lib/voz.js +75 -223
  71. package/package.json +2 -3
  72. package/skills/motion-de-primeira/SKILL.md +5 -5
  73. package/studio/narrar.py +10 -7
  74. package/studio/rastrear.py +399 -0
  75. package/studio/texto.py +208 -0
  76. package/voz/primo_voz/__main__.py +96 -236
  77. package/voz/primo_voz/transcrever.py +31 -1
  78. package/app/ansi.js +0 -55
  79. package/app/app.css +0 -500
  80. package/app/app.js +0 -1017
  81. package/app/icone.svg +0 -6
  82. package/app/index.html +0 -220
  83. package/app/primo-marca.js +0 -192
  84. package/lib/app.js +0 -615
  85. package/lib/icone.js +0 -263
  86. package/lib/instalar-app.js +0 -231
  87. package/lib/painel.js +0 -291
  88. package/voz/primo_voz/desperta.py +0 -195
  89. package/voz/primo_voz/orbe.py +0 -406
@@ -34,7 +34,7 @@ const tokens = (t) => String(t || '').normalize('NFD').replace(/[̀-ͯ]/g, '').t
34
34
  /** Quanto tempo um texto precisa ficar na tela para ser lido. Nunca menos que
35
35
  0,3 s por palavra: a regra de legibilidade do /brag (Arthur, 28/09). */
36
36
  const nPalavras = (t) => String(t || '').split(/\s+/).filter(Boolean).length;
37
- const leitura = (t) => Math.max(0.55 + 0.22 * nPalavras(t), 0.3 * nPalavras(t));
37
+ const leituraCheia = (t) => Math.max(0.55 + 0.22 * nPalavras(t), 0.3 * nPalavras(t));
38
38
 
39
39
  /** A fala desta parte (o que a voz diz): a "fala", ou os textos da tela. */
40
40
  /* A voz lê EXATAMENTE o que está na tela, na ordem da tela ("a narração tem
@@ -94,12 +94,23 @@ function quando(textos, palavras, dur, t0 = 0.25) {
94
94
  });
95
95
  }
96
96
 
97
- function montarCinema({ roteiro, largura = 1920, altura = 1080, tempos = [], narracao = null, sons = {}, trilha = null, fotos = [], fortes = {} }) {
97
+ function montarCinema({ roteiro, largura = 1920, altura = 1080, tempos = [], narracao = null, sons = {}, trilha = null, fotos = [], fortes = {}, ritmoLeitura = 1 }) {
98
+ /* O tempo de ler cada texto, escalado: o cinema puro dá o tempo inteiro
99
+ (1); o híbrido, que tem b-roll trocando por baixo e texto curto, dá
100
+ pouco mais da metade — senão a cena espera muito depois da voz. */
101
+ const leitura = (t) => leituraCheia(t) * ritmoLeitura;
98
102
  const partes = (roteiro && roteiro.partes) || [];
99
103
  if (!partes.length) return { ok: false, error: 'o roteiro veio sem partes.' };
100
104
  const marca = roteiro.marca || {};
101
- const grad = marca.gradiente && marca.gradiente.length >= 2 ? marca.gradiente : ['#2E81ED', '#9567DB'];
102
- const PRETO = (marca.cores && marca.cores.fundo) || '#07070A';
105
+ /* O destaque é o da MARCA: sem gradiente declarado, a cor de destaque dela
106
+ vira o gradiente (antes caía no roxo da Paris em toda marca). */
107
+ const corD = marca.cores && /^#[0-9a-f]{6}$/i.test(marca.cores.destaque || '') ? marca.cores.destaque : null;
108
+ const grad = marca.gradiente && marca.gradiente.length >= 2 ? marca.gradiente : corD ? [corD, corD] : ['#2E81ED', '#9567DB'];
109
+ /* O cinema é ESCURO: o texto é branco. Marca de fundo claro (a Anthropic é
110
+ branca) deixava o texto invisível — o fundo dela só vale se for escuro. */
111
+ const luz = (h) => { const n = parseInt(String(h).slice(1), 16); return ((n >> 16 & 255) * 0.299 + (n >> 8 & 255) * 0.587 + (n & 255) * 0.114) / 255; };
112
+ const fundoMarca = marca.cores && /^#[0-9a-f]{6}$/i.test(marca.cores.fundo || '') ? marca.cores.fundo : null;
113
+ const PRETO = fundoMarca && luz(fundoMarca) < 0.25 ? fundoMarca : '#07070A';
103
114
  const BRANCO = '#F5F5F7';
104
115
  const DEST = grad[1];
105
116
  const F = { titulo: (marca.fontes && marca.fontes.titulo) || 'Inter', texto: (marca.fontes && marca.fontes.texto) || 'Inter' };
@@ -279,12 +290,19 @@ function montarCinema({ roteiro, largura = 1920, altura = 1080, tempos = [], nar
279
290
  vol(t, 0.3);
280
291
  for (let a = 0; a < dur; a += 1.2) pFoto(els, foto(), a, Math.min(dur, a + 1.2), { veu: 0.45 });
281
292
  textosDaParte.forEach((x, k) => {
282
- const m = String(x).match(/^(\d+)\s+(.*)$/);
293
+ /* O valor pode vir com prefixo e unidade: "até 30% mais barato", "+120
294
+ marcas", "2,5x mais rápido". O contador sobe só a parte numérica e
295
+ mantém o resto em volta (contar.antes / contar.depois). */
296
+ const mv = String(x).match(/^((?:at[ée]|mais de|\+|-)?\s*)(\d+(?:[.,]\d+)?)(\s?(?:%|x|mil|mi|bi|k|h|s|min)?)\s+(.*)$/i);
297
+ const m = mv ? [x, mv[2], mv[4]] : null;
283
298
  if (k === 0 && p.antes) { pTexto(els, x, inicios[k], fimDe(k), { max: base * 0.08, peso: 700, pal: palDo(x) }); return; }
284
299
  if (!m) { pTexto(els, x, inicios[k], fimDe(k), { pal: palDo(x) }); return; }
285
- const tam = Math.round(deitado ? H * 0.24 : W * 0.3);
286
- els.push({ tipo: 'texto', texto: m[1], fonte: F.titulo, peso: 800, tamanho: tam, cor: BRANCO, x: M, y: Math.round(H * 0.5 - tam * 0.75), largura: W0, alinhamento: 'center',
287
- de: inicios[k], ate: fimDe(k), contar: { ate: Number(m[1]), dur: 0.45 }, gradienteTexto: undefined,
300
+ // O número inteiro numa linha: "ATÉ 30%" em pé quebrava em duas, por cima do rótulo.
301
+ const larguraNum = String(mv[1] + m[1] + mv[3]).length * 0.8; // serifa larga: 0,62 ainda quebrava
302
+ const tam = Math.round(Math.min(deitado ? H * 0.24 : W * 0.3, (W0 * 0.96) / Math.max(1, larguraNum)));
303
+ const casas = /[.,]/.test(m[1]) ? 1 : 0;
304
+ els.push({ tipo: 'texto', texto: mv[1] + m[1] + mv[3], fonte: F.titulo, peso: 800, tamanho: tam, cor: BRANCO, x: M, y: Math.round(H * 0.5 - tam * 0.75), largura: W0, alinhamento: 'center',
305
+ de: inicios[k], ate: fimDe(k), contar: { ate: Number(m[1].replace(',', '.')), dur: 0.6, casas, antes: mv[1].toUpperCase(), depois: mv[3] }, gradienteTexto: undefined,
288
306
  anima: { opacidade: [{ t: 0, v: 0 }, { t: 0.25, v: 1 }], escala: [{ t: 0, v: 0.92 }, { t: 0.6, v: 1, e: 'saida' }] } });
289
307
  pTexto(els, m[2], inicios[k] + 0.1, fimDe(k), { y: H * 0.5 + tam * 0.3, max: base * 0.085, peso: 700, cor: DEST });
290
308
  });
@@ -398,4 +416,4 @@ function montarCinema({ roteiro, largura = 1920, altura = 1080, tempos = [], nar
398
416
  };
399
417
  }
400
418
 
401
- module.exports = { montarCinema, falaDe, paraFala, quando, leitura };
419
+ module.exports = { montarCinema, falaDe, paraFala, quando, leitura: leituraCheia };
@@ -62,6 +62,26 @@ function precisaDeProducao(texto) {
62
62
  return { producao: false };
63
63
  }
64
64
 
65
+ /* Editar a GRAVAÇÃO da pessoa não é produzir vídeo do zero: aí o caminho é o
66
+ editor por linha do tempo (`primocode video`, lib/video/), não o roteiro. */
67
+ const PEDE_EDICAO = /\b(edit[ae]r?|edi[cç][aã]o|cort[ae]r?|legend[ae]r?|tir[ae]r?\s+(o|os)\s+sil[eê]ncios?)\b[\s\S]{0,80}\b(grava[cç][aã]o|meu\s+v[ií]deo|esse\s+v[ií]deo|este\s+v[ií]deo|[\w-]+\.(mp4|mov|mkv|webm|m4v))\b|\b[\w-]+\.(mp4|mov|mkv|webm|m4v)\b[\s\S]{0,80}\b(edit|cort|legend|sil[eê]nci)/i;
68
+
69
+ /* Os pedidos de EFEITO sobre a gravação (lib/video/ops-fx.js): fundo, camadas,
70
+ estalo, objeto na mão, legenda na cabeça. Falam da pessoa ("me", "minha
71
+ mão"), não do arquivo — por isso não casavam com a regra de cima. */
72
+ const PEDE_EFEITO = /\b(tir[ae]r?|remov\w*|troc\w*)\s+(o\s+)?fundo\b|\bsepar\w*\s+em\s+camadas\b|\bestal\w*\s+(o\s+)?dedo\b|\b(logo|objeto)\b[\s\S]{0,40}\bm[aã]os?\b|\bjunt\w*\s+as\s+m[aã]os\b|\blegenda\b[\s\S]{0,40}\b(azul|vermelh|verde|amarel|rosa|roxa|branc|cabe[cç]a|em cima|embaixo)/i;
73
+
74
+ /* "edite: /Users/…/Fototeca do Photo Booth/…/Filme em 30-09-2026 às 23.56.mov": o caminho
75
+ do Photo Booth tem mais de 80 caracteres, e o pedido ia para o Estúdio de
76
+ motion — que tratava a gravação como b-roll (01/10). Verbo de editar + um
77
+ arquivo de vídeo em qualquer lugar da frase é edição. */
78
+ const EDITA_ARQUIVO = /\b(edit\w*|edi[cç][aã]o|p[oõ]e\s+(ele\s+)?pra\s+editar)\b[\s\S]*\.(mp4|mov|mkv|webm|m4v)\b|\.(mp4|mov|mkv|webm|m4v)\b[\s\S]*\b(edit\w*|edi[cç][aã]o)\b/i;
79
+
80
+ function precisaDeEdicao(texto) {
81
+ const t = String(texto || '');
82
+ return PEDE_EDICAO.test(t) || PEDE_EFEITO.test(t) || EDITA_ARQUIVO.test(t);
83
+ }
84
+
65
85
  // ── 2. as decisões que não são do modelo ─────────────────────────────────
66
86
 
67
87
  /**
@@ -293,7 +313,7 @@ function pauta(plano) {
293
313
  }
294
314
 
295
315
  module.exports = {
296
- precisaDeProducao, vozPara, ritmoPara,
316
+ precisaDeProducao, precisaDeEdicao, vozPara, ritmoPara,
297
317
  normalizarPlano, extrairJSON, planejar, pauta,
298
318
  DIRETOR, REVISAO,
299
319
  };
@@ -60,13 +60,18 @@ function lerLista(html) {
60
60
  }).filter((f) => f.titulo && f.segundos > 0);
61
61
  }
62
62
 
63
- /** A faixa que cobre o vídeo e mais combina com o clima. Nenhuma cobre: null. */
64
- function escolher(faixas, { clima = 'neutro', segundos = 0 } = {}) {
63
+ /** A faixa que cobre o vídeo e mais combina com o clima. Nenhuma cobre: null.
64
+ * Com `semente`, sorteia entre as 5 melhores que não estão em `evitar` e não
65
+ * têm voz nas tags — "não repetir a mesma faixa em todo vídeo" (30/09). */
66
+ function escolher(faixas, { clima = 'neutro', segundos = 0, semente = null, evitar = [] } = {}) {
65
67
  const palavras = CLIMAS[clima] || CLIMAS.neutro;
66
68
  const nota = (f) => f.tags.reduce((n, t) => n + palavras.filter((p) => t.includes(p)).length, 0);
67
69
  const cabem = (faixas || []).filter((f) => f.segundos >= segundos);
68
70
  if (!cabem.length) return null;
69
- return cabem.reduce((a, b) => (nota(b) > nota(a) ? b : a));
71
+ if (semente == null) return cabem.reduce((a, b) => (nota(b) > nota(a) ? b : a));
72
+ const semVoz = cabem.filter((f) => !f.tags.some((t) => /vocal|singing|voice|rap\b|song/.test(t)) && !evitar.includes(f.id));
73
+ const pool = (semVoz.length ? semVoz : cabem).slice().sort((a, b) => nota(b) - nota(a)).slice(0, 5);
74
+ return pool[Math.abs(Number(semente) | 0) % pool.length];
70
75
  }
71
76
 
72
77
  async function buscarPadrao(url) {
@@ -93,8 +98,14 @@ function provedor({ buscar = buscarPadrao, baixar, pasta = 'midia' } = {}) {
93
98
  const salvar = baixar || baixarEm(pasta);
94
99
  return {
95
100
  nome: 'ende', tipo: 'musica',
96
- async gerar({ clima, segundos = 0 } = {}) {
97
- const faixa = escolher(lerLista(await buscar(LISTA)), { clima, segundos });
101
+ async gerar({ clima, segundos = 0, genero = null, semente = null, evitar = [] } = {}) {
102
+ // O GÊNERO (ende.app/en/genres/<genero>): jazz, lofi, funk, ambient… — escolhido pelo assunto.
103
+ const url = genero ? 'https://ende.app/en/genres/' + encodeURIComponent(genero) : LISTA;
104
+ let faixas = [];
105
+ // A página do gênero falhou: a busca geral (que, sem rede, lança — e a corrente cai no próximo provedor).
106
+ if (genero) { try { faixas = lerLista(await buscar(url)); } catch { faixas = []; } }
107
+ if (!faixas.length) faixas = lerLista(await buscar(LISTA));
108
+ const faixa = escolher(faixas, { clima, segundos, semente, evitar });
98
109
  if (!faixa) return null;
99
110
  const meta = {
100
111
  fonte: faixa.pagina, titulo: faixa.titulo, autor: 'Sascha Ende',
@@ -0,0 +1,66 @@
1
+ /**
2
+ * estilo.js — qual estilo de vídeo um roteiro vira.
3
+ *
4
+ * gerativo O MOTION: sem template, a tipografia é o filme, numa linha do tempo
5
+ * contínua sorteada do roteiro (gerativo.js). É o que "motion" e
6
+ * "animação" pedem, e o que o modo "sem referências" usa.
7
+ * motion o motion antigo: o protagonista 3D que se deforma (motion.js),
8
+ * só pelo nome (classico, limpo).
9
+ * vox explainer editorial: tinta sobre papel, os oito dispositivos VOX (vox.js)
10
+ * marca brand reveal: o brilho, o vetor, a marca cromada, o padrão (lancamento.js)
11
+ * lancamento filme de lançamento de produto: cartões, painel, calendário, mural (lancamento.js)
12
+ * impacto anúncio: corte a cada batida (impacto.js)
13
+ * cinema filme de marca premium com fotos reais (cinema.js)
14
+ * hibrido O PADRÃO do vídeo: o cinema com o kit por cima — foto e vídeo reais
15
+ * que não param, e o motion por cima (hibrido.js)
16
+ * composicao a composição antiga, uma cena por parte (composicao.js)
17
+ *
18
+ * Quem manda, em ordem: o estilo que o AGENTE pediu; o do roteiro (a menos
19
+ * que o pedido diga motion); o que o pedido descreve; e o motion.
20
+ */
21
+
22
+ 'use strict';
23
+
24
+ const ESTILOS = ['hibrido', 'gerativo', 'motion', 'vox', 'marca', 'lancamento', 'impacto', 'cinema', 'composicao'];
25
+ // Nomes que o modelo usa para os mesmos estilos.
26
+ /* "motion" agora é o GERATIVO ("não pode ter template", 29–30/09); o motion
27
+ antigo segue pelo nome "classico" (e "limpo", o nome mais antigo dele). */
28
+ const APELIDOS = { motion: 'gerativo', generativo: 'gerativo', kinetic: 'gerativo', cinetico: 'gerativo', 'cinético': 'gerativo',
29
+ tipografico: 'gerativo', 'tipográfico': 'gerativo', animacao: 'gerativo', 'animação': 'gerativo',
30
+ classico: 'motion', 'clássico': 'motion', 'motion-classico': 'motion', motion3d: 'motion',
31
+ absurdo: 'hibrido', misto: 'hibrido', mix: 'hibrido', 'híbrido': 'hibrido', limpo: 'motion', explainer: 'vox', editorial: 'vox', brand: 'marca', branding: 'marca', 'brand-reveal': 'marca', launch: 'lancamento', 'lançamento': 'lancamento', produto: 'lancamento' };
32
+
33
+ const PEDE_MOTION = /\bmotion\b|anima[çc][ãa]o|animad[oa]/i;
34
+ const PEDE_VOX = /\bvox\b|explica|explainer|document[aá]rio|editorial|hist[oó]ria d[aoe]s?\b/i;
35
+ const PEDE_MARCA = /\bbrand|identidade visual|revela[çc][ãa]o d[ae] marca|\blogo(tipo|marca)?\b|vinheta de marca/i;
36
+ const PEDE_LANCAMENTO = /lan[çc]amento|\blaunch|\bsaas\b|produto novo|apresenta(ndo|r|ção d[oe]) (o |a )?(app|produto|plataforma|ferramenta)/i;
37
+ const PEDE_IMPACTO = /an[uú]nci|reels?|divulg|promo|lan[çc]a|vend|campanha|convite|inscri/i;
38
+
39
+ const normal = (e) => {
40
+ const x = String(e || '').trim().toLowerCase();
41
+ if (!x) return null;
42
+ const n = APELIDOS[x] || x;
43
+ return ESTILOS.includes(n) ? n : null;
44
+ };
45
+
46
+ function escolherEstilo({ pedido = '', agente = null, roteiro = null } = {}) {
47
+ const pedidoAgente = normal(agente);
48
+ if (pedidoAgente) return pedidoAgente;
49
+ const doRoteiro = normal(roteiro);
50
+ /* Pedir MOTION (ou animação) é pedir o gerativo — antes do vox, da marca e
51
+ do lançamento, que são leiaute fixo ("não pode ter template", 30/09). */
52
+ if (doRoteiro && !(doRoteiro !== 'gerativo' && PEDE_MOTION.test(pedido))) return doRoteiro;
53
+ if (PEDE_MOTION.test(pedido)) return 'gerativo';
54
+ if (PEDE_VOX.test(pedido)) return 'vox';
55
+ if (PEDE_MARCA.test(pedido)) return 'marca';
56
+ if (PEDE_LANCAMENTO.test(pedido)) return 'lancamento';
57
+ /* Anúncio, reels e o resto: o HÍBRIDO. "Um mix da versão antiga (o reels
58
+ da imersão, no cinema) e da última (o kit) — absurdo, que nem parece
59
+ que foi IA" (29/09). O impacto segue existindo, se pedido pelo nome. */
60
+ return 'hibrido';
61
+ }
62
+
63
+ /** Os estilos que usam os efeitos leves e a trilha do ende.app (os de motion, não os de corte). */
64
+ const LEVES = new Set(['gerativo', 'motion', 'vox', 'marca', 'lancamento']);
65
+
66
+ module.exports = { escolherEstilo, ESTILOS, LEVES, PEDE_MOTION };