primocode 10.0.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 (55) hide show
  1. package/README.md +2 -2
  2. package/bin/primocode.js +69 -1
  3. package/lib/act.js +118 -19
  4. package/lib/browser.js +89 -5
  5. package/lib/catalogo.js +1 -1
  6. package/lib/claude-engine.js +38 -17
  7. package/lib/codex-engine.js +27 -9
  8. package/lib/entrada.js +73 -9
  9. package/lib/estudio/cinema.js +27 -9
  10. package/lib/estudio/diretor.js +13 -1
  11. package/lib/estudio/ende.js +16 -5
  12. package/lib/estudio/estilo.js +23 -8
  13. package/lib/estudio/gerativo.js +1453 -0
  14. package/lib/estudio/hibrido.js +165 -0
  15. package/lib/estudio/index.js +244 -36
  16. package/lib/estudio/kit/gerativo.jsx +980 -0
  17. package/lib/estudio/kit/index.jsx +3 -1
  18. package/lib/estudio/kit/tres.jsx +308 -0
  19. package/lib/estudio/kit.js +7 -1
  20. package/lib/estudio/lancamento.js +28 -0
  21. package/lib/estudio/referencias.js +105 -0
  22. package/lib/estudio/remotion.js +53 -9
  23. package/lib/estudio/ritmo.js +2 -2
  24. package/lib/estudio/sons-leves.js +38 -1
  25. package/lib/estudio/variedade.js +105 -0
  26. package/lib/estudio/vox.js +37 -8
  27. package/lib/mcp.js +156 -0
  28. package/lib/personas.js +67 -0
  29. package/lib/skills.js +36 -0
  30. package/lib/tools.js +22 -3
  31. package/lib/ui.js +41 -6
  32. package/lib/video/auto.js +329 -0
  33. package/lib/video/camadas.js +207 -0
  34. package/lib/video/cli.js +1 -1
  35. package/lib/video/filters.js +39 -6
  36. package/lib/video/fx3d/Camadas.jsx +64 -0
  37. package/lib/video/fx3d/Objetos.jsx +128 -0
  38. package/lib/video/fx3d/Root.jsx +18 -0
  39. package/lib/video/fx3d/modelos.jsx +304 -0
  40. package/lib/video/fx3d.js +108 -0
  41. package/lib/video/gerados.js +119 -0
  42. package/lib/video/objetos.js +111 -0
  43. package/lib/video/ops-fx.js +453 -0
  44. package/lib/video/ops.js +30 -5
  45. package/lib/video/paralelo.js +50 -0
  46. package/lib/video/pedido.js +191 -0
  47. package/lib/video/rastrear.js +95 -0
  48. package/lib/video/rastro.js +276 -0
  49. package/lib/video/render.js +67 -14
  50. package/lib/video/texto.js +6 -4
  51. package/lib/video/timeline.js +9 -1
  52. package/package.json +2 -2
  53. package/skills/motion-de-primeira/SKILL.md +5 -4
  54. package/studio/narrar.py +10 -7
  55. package/studio/rastrear.py +399 -0
@@ -0,0 +1,119 @@
1
+ /**
2
+ * gerados.js — na hora do export (ou do inspect), o que a timeline PEDE e
3
+ * ainda não existe em disco: as camadas 3D renderizadas no Remotion, e a
4
+ * posição de cada legenda que segue a cabeça.
5
+ *
6
+ * Uma operação de efeito só escreve a receita (rápido, com undo). O render
7
+ * pesado acontece uma vez, aqui, e fica no disco pelo hash da receita:
8
+ * desfazer e refazer não renderiza de novo.
9
+ */
10
+
11
+ 'use strict';
12
+
13
+ const fs = require('fs');
14
+ const path = require('path');
15
+ const T = require('./timeline.js');
16
+ const C = require('./camadas.js');
17
+ const R = require('./rastro.js');
18
+ const RT = require('./rastrear.js');
19
+ const OB = require('./objetos.js');
20
+ const FX = require('./fx3d.js');
21
+ const video = require('../estudio/video.js');
22
+
23
+ function propsObjetos(tl, a, rastro) {
24
+ const baseAsset = tl.assets[a.gerado.base];
25
+ const clip = C.clipesBase(tl, baseAsset.id)[0];
26
+ const tela = clip ? (x, y) => R.origemParaTela(tl, clip, baseAsset, x, y) : (x, y) => ({ x, y });
27
+ const p = OB.plano({ receita: a.gerado, trilhas: R.trilhasDasMaos(rastro), rostos: (rastro.quadros || []).map((q) => (q && q.f) || null), rfps: rastro.fps, fps: tl.fps, tela, asp: tl.width / tl.height });
28
+ return { ...p, fps: tl.fps, w: tl.width, h: tl.height };
29
+ }
30
+
31
+ /** Os quadros da pessoa recortada (PNG com alfa) e a placa do fundo, no public/ do projeto Remotion. */
32
+ async function quadrosDasCamadas(raiz, tl, a, arqs) {
33
+ const g = a.gerado;
34
+ const baseAsset = tl.assets[g.base];
35
+ const dir = path.join(raiz, 'public', a.id + '-' + FX.hash(g));
36
+ fs.rmSync(dir, { recursive: true, force: true });
37
+ fs.mkdirSync(dir, { recursive: true });
38
+ const n = Math.max(1, Math.round((g.ate - g.de) * tl.fps));
39
+ const W = tl.width;
40
+ const H = tl.height;
41
+ const encaixe = `scale=${W}:${H}:force_original_aspect_ratio=increase,crop=${W}:${H}`;
42
+ const ss = Math.max(0, g.de - 0.5 / (baseAsset.fps || tl.fps)).toFixed(6);
43
+ const r = await video.rodar(video.ffmpeg(), ['-hide_banner', '-loglevel', 'error', '-y',
44
+ '-ss', ss, '-i', baseAsset.path, '-ss', ss, '-i', arqs.matte,
45
+ '-filter_complex', `[0:v]fps=${tl.fps},${encaixe}[v];[1:v]format=gray,fps=${tl.fps},${encaixe}[m];[v][m]alphamerge[p]`,
46
+ '-map', '[p]', '-frames:v', String(n), '-compression_level', '1', path.join(dir, 'p%04d.png')]);
47
+ if (!r.ok) return { ok: false, error: `não extraí os quadros das camadas: ${r.erro.slice(-300)}` };
48
+ const r2 = await video.rodar(video.ffmpeg(), ['-hide_banner', '-loglevel', 'error', '-y', '-i', arqs.fundo, '-vf', encaixe, path.join(dir, 'fundo.png')]);
49
+ if (!r2.ok) return { ok: false, error: 'não preparei a placa do fundo' };
50
+ return { ok: true, dir, rel: path.basename(dir), n };
51
+ }
52
+
53
+ /** Renderiza o que falta. Devolve {ok, feitos, tookS} ou o erro. */
54
+ async function garantir(pasta, tl, { aoFalar = () => {} } = {}) {
55
+ const falta = Object.values(tl.assets).filter((a) => a.gerado && !fs.existsSync(a.path));
56
+ if (!falta.length) return { ok: true, feitos: 0 };
57
+ const inicio = Date.now();
58
+ const pj = await FX.projeto(pasta);
59
+ if (!pj.ok) return { ok: false, error: `o Remotion do editor não subiu: ${pj.error}` };
60
+ for (const a of falta) {
61
+ const baseAsset = tl.assets[a.gerado.base];
62
+ if (!baseAsset) return { ok: false, error: `a camada ${a.id} aponta para a mídia ${a.gerado.base}, que não existe` };
63
+ const rs = await RT.garantir(pasta, baseAsset);
64
+ if (!rs.ok) return rs;
65
+ aoFalar(`renderizando ${a.nome} (Remotion, ${FX.trabalhadores()} trabalhadores)`);
66
+ let r;
67
+ if (a.gerado.tipo === 'objetos') {
68
+ r = await FX.renderizar(pj.raiz, 'Objetos', propsObjetos(tl, a, rs.rastro), a.path, { alfa: true });
69
+ } else if (a.gerado.tipo === 'camadas') {
70
+ const q = await quadrosDasCamadas(pj.raiz, tl, a, rs.arquivos);
71
+ if (!q.ok) return q;
72
+ r = await FX.renderizar(pj.raiz, 'Camadas', { n: q.n, fps: tl.fps, w: tl.width, h: tl.height, dir: q.rel, fundo: `${q.rel}/fundo.png`, rotulos: a.gerado.rotulos || null }, a.path);
73
+ fs.rmSync(q.dir, { recursive: true, force: true });
74
+ } else r = { ok: false, error: `tipo gerado "${a.gerado.tipo}"` };
75
+ if (!r.ok) return { ok: false, error: `o render 3D falhou (${a.nome}): ${r.error}` };
76
+ }
77
+ return { ok: true, feitos: falta.length, tookS: Math.round((Date.now() - inicio) / 100) / 10 };
78
+ }
79
+
80
+ // ── a legenda que segue a cabeça ─────────────────────────────────────────
81
+
82
+ const Q = (v) => Math.round(v / 0.004) * 0.004; // passo de ~8 px: menos estados de PNG, sem tremer
83
+
84
+ /**
85
+ * Uma função (clipe, quadroDaTimeline) → {cx, cy} | null para as legendas
86
+ * com `text.follow`, ou null se nenhuma segue. `avisos` recebe o que faltou.
87
+ */
88
+ function posicoesDeLegenda(pasta, tl, avisos = []) {
89
+ const seguem = T.todosOsClipes(tl).some((x) => x.clip.text && x.clip.text.follow);
90
+ if (!seguem) return null;
91
+ const cabecas = {};
92
+ const cabecaDe = (assetId) => {
93
+ if (assetId in cabecas) return cabecas[assetId];
94
+ const a = tl.assets[assetId];
95
+ const arq = a && RT.arquivos(pasta, a).rastro;
96
+ if (!arq || !fs.existsSync(arq)) { avisos.push(`legenda na cabeça: a mídia ${assetId} não foi rastreada (rode track)`); return (cabecas[assetId] = null); }
97
+ const rastro = RT.lerRastro(arq);
98
+ return (cabecas[assetId] = { rastro, trilha: R.trilhaDaCabeca(rastro) });
99
+ };
100
+ return (c, f) => {
101
+ const fw = c.text && c.text.follow;
102
+ if (!fw) return null;
103
+ const cab = cabecaDe(fw.asset);
104
+ if (!cab) return null;
105
+ const clip = C.clipeNoQuadro(tl, fw.asset, f);
106
+ if (!clip) return null;
107
+ const seg = C.quadroParaOrigem(tl, fw.asset, f);
108
+ const i = Math.max(0, Math.min(cab.trilha.length - 1, Math.round(seg * cab.rastro.fps)));
109
+ const h = cab.trilha[i];
110
+ if (!h) return null;
111
+ const asset = tl.assets[fw.asset];
112
+ const topo = R.origemParaTela(tl, clip, asset, h.cx, h.top);
113
+ const tam = (c.text.style && c.text.style.size) || 0.048;
114
+ const p = R.acimaDaCabeca({ cx: topo.x, top: topo.y }, { altura: tam * 1.45, largura: Math.min(c.transform.w, 0.9), margem: fw.margem ?? 0.02 });
115
+ return p && { cx: Q(p.cx), cy: Q(p.cy) };
116
+ };
117
+ }
118
+
119
+ module.exports = { garantir, propsObjetos, posicoesDeLegenda };
@@ -0,0 +1,111 @@
1
+ /**
2
+ * objetos.js — a receita dos objetos na mão vira, quadro a quadro, ONDE
3
+ * cada um está (as props do fx3d/Objetos.jsx). Conta pura: entra a receita
4
+ * (segundos da origem) e as trilhas das mãos, sai posição e tamanho.
5
+ *
6
+ * · o objeto flutua ACIMA da palma (um tanto do tamanho da palma), e não
7
+ * em cima dela: a mão aberta segura o objeto no ar;
8
+ * · o TAMANHO é fixo durante a vida do objeto (a mediana da palma): a
9
+ * palma medida pulsa com o gesto, e um logo que incha e murcha a cada
10
+ * quadro parece defeito, não perspectiva;
11
+ * · quadro sem mão segura a última posição — o objeto não some porque o
12
+ * detector piscou;
13
+ * · nunca sai da tela: quem fala para a câmera tem a mão na borda de
14
+ * baixo, e o objeto "acima da palma" ficaria cortado.
15
+ */
16
+
17
+ 'use strict';
18
+
19
+ const r4 = (n) => Math.round(n * 10000) / 10000;
20
+
21
+ function mediana(v) {
22
+ const s = v.filter((x) => Number.isFinite(x)).sort((a, b) => a - b);
23
+ return s.length ? s[Math.floor(s.length / 2)] : null;
24
+ }
25
+
26
+ /** A trilha de uma mão no intervalo [i0, i1) de quadros da ORIGEM, segurando o último valor. */
27
+ function segurar(trilha, i0, i1) {
28
+ const out = [];
29
+ let ult = null;
30
+ for (let i = i0; i < i1; i++) { const h = trilha[i] || null; if (h) ult = h; out.push(ult); }
31
+ // O começo sem mão herda a primeira que aparecer.
32
+ const primeira = out.find(Boolean) || null;
33
+ return out.map((h) => h || primeira);
34
+ }
35
+
36
+ /**
37
+ * {receita, trilhas:{left,right}, rfps (fps do rastro), fps (do projeto),
38
+ * tela(x,y) → {x,y} (origem → quadro do projeto), asp (largura/altura do projeto)}
39
+ * → {n, objetos:[{modelo,cor,texto,f0,f1,trilha:[[x,y,s]|0],funde}], fusao?}
40
+ */
41
+ function plano({ receita, trilhas, rfps, fps, tela = (x, y) => ({ x, y }), asp = 9 / 16, rostos = null }) {
42
+ const n = Math.max(1, Math.round((receita.ate - receita.de) * fps));
43
+ const iDe = (seg) => Math.round((seg - receita.de) * fps); // segundo da origem → quadro da camada
44
+ const iOrig = (f) => Math.round((receita.de + f / fps) * rfps); // quadro da camada → quadro do rastro
45
+ const posicao = (h, tam) => {
46
+ const p = tela(h.x, h.y - 1.2 * h.s);
47
+ // A placa do objeto tem ~1,2× o "tamanho" de largura (girando, um pouco mais).
48
+ const mx = (1.25 * tam / asp) / 2 + 0.015;
49
+ const my = 1.25 * tam / 2 + 0.015;
50
+ return [r4(Math.max(mx, Math.min(1 - mx, p.x))), r4(Math.max(my, Math.min(0.93 - my, p.y))), r4(tam)];
51
+ };
52
+ const fusao = receita.fusao || null;
53
+ const fj = fusao && fusao.junta !== null && fusao.junta !== undefined ? iDe(fusao.junta) : null;
54
+ const fm = fj !== null ? fj + Math.round(0.4 * fps) : null;
55
+ const objetos = [];
56
+ let tamBase = null;
57
+ for (const o of receita.objetos || []) {
58
+ const trilha = trilhas[o.mao] || trilhas.right;
59
+ const f0 = Math.max(0, iDe(o.de));
60
+ let f1 = Math.min(n, iDe(o.ate));
61
+ const funde = fj !== null && f0 < fj && f1 > fj;
62
+ if (funde) f1 = Math.min(n, fm);
63
+ if (f1 <= f0) continue;
64
+ const brutos = segurar(trilha, iOrig(f0), iOrig(f1) + 1);
65
+ const tam = Math.max(0.085, Math.min(0.2, 1.45 * (mediana(brutos.map((h) => h && h.s)) || 0.08)));
66
+ tamBase = tamBase || tam;
67
+ const lista = [];
68
+ for (let f = f0; f < f1; f++) {
69
+ const h = brutos[iOrig(f) - iOrig(f0)] || brutos[brutos.length - 1];
70
+ lista.push(h ? posicao(h, tam) : 0);
71
+ }
72
+ if (!lista.some(Boolean)) continue;
73
+ objetos.push({ modelo: o.modelo, ...(o.cor ? { cor: o.cor } : {}), ...(o.texto ? { texto: o.texto } : {}), f0, f1, trilha: lista, ...(funde ? { funde: true } : {}) });
74
+ }
75
+ const out = { n, objetos };
76
+ const fundidos = objetos.filter((o) => o.funde);
77
+ if (fusao && fj !== null && fundidos.length) {
78
+ // O meio das duas mãos (ou a mão que houver), um pouco acima: onde a fusão acontece e o cérebro nasce.
79
+ const ate = Math.min(n, fusao.depoisAte !== undefined ? iDe(fusao.depoisAte) : n);
80
+ const L = segurar(trilhas.left, iOrig(fj), iOrig(ate) + 1);
81
+ const R = segurar(trilhas.right, iOrig(fj), iOrig(ate) + 1);
82
+ const tam = Math.min(0.26, (tamBase || 0.12) * 1.6);
83
+ const meio = [];
84
+ for (let f = fj; f < ate; f++) {
85
+ const k = iOrig(f) - iOrig(fj);
86
+ const a = L[k];
87
+ const b = R[k];
88
+ const h = a && b ? { x: (a.x + b.x) / 2, y: (a.y + b.y) / 2, s: (a.s + b.s) / 2 } : a || b;
89
+ let q = h ? posicao({ ...h, y: h.y - 0.25 * h.s }, tam) : 0;
90
+ /* As mãos abertas ficam na altura do rosto (o gesto de "nasceu"), e o
91
+ meio delas é a cara da pessoa: o cérebro desce para logo abaixo do
92
+ queixo. Visto no primeiro vídeo de verdade (01/10). */
93
+ const fc = q && rostos && rostos[iOrig(f)];
94
+ if (fc) {
95
+ const a0 = tela(fc[0], fc[1]);
96
+ const a1 = tela(fc[2], fc[3]);
97
+ const cobre = q[0] + tam / asp / 2 > a0.x && q[0] - tam / asp / 2 < a1.x && q[1] - tam / 2 < a1.y && q[1] + tam / 2 > a0.y;
98
+ if (cobre) q = [q[0], r4(Math.min(0.93 - 1.25 * tam / 2 - 0.015, a1.y + 0.62 * tam)), q[2]];
99
+ }
100
+ meio.push(q);
101
+ }
102
+ out.fusao = { fj, fm, trilha: meio };
103
+ if (fusao.depois) {
104
+ const fa = fusao.abre !== null && fusao.abre !== undefined ? Math.max(fm, iDe(fusao.abre)) : fm + Math.round(0.3 * fps);
105
+ if (fa < ate) objetos.push({ modelo: fusao.depois, f0: fa, f1: ate, trilha: meio.slice(fa - fj) });
106
+ }
107
+ }
108
+ return out;
109
+ }
110
+
111
+ module.exports = { plano, segurar, mediana };