primocode 10.4.7 → 10.6.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.
- package/README.md +2 -2
- package/bin/primocode.js +117 -24
- package/lib/act.js +141 -9
- package/lib/agents-md.js +140 -0
- package/lib/auditoria.js +122 -0
- package/lib/browser.js +48 -1
- package/lib/catalogo.js +29 -5
- package/lib/comandos-extra.js +2 -0
- package/lib/config.js +32 -4
- package/lib/conta.js +9 -3
- package/lib/entrada.js +33 -11
- package/lib/estudio/colecao.js +218 -0
- package/lib/estudio/dados/fluent-3d.json +1 -0
- package/lib/estudio/dados/noto-animado.json +1 -0
- package/lib/estudio/diretor.js +1 -0
- package/lib/estudio/estilo.js +6 -2
- package/lib/estudio/falantes.js +30 -6
- package/lib/estudio/gerativo.js +8 -0
- package/lib/estudio/hibrido.js +144 -8
- package/lib/estudio/index.js +202 -83
- package/lib/estudio/kit/animacao.jsx +41 -0
- package/lib/estudio/kit/gerativo.jsx +21 -1
- package/lib/estudio/kit/index.jsx +3 -1
- package/lib/estudio/kit/mundo.jsx +1060 -0
- package/lib/estudio/kit/voxel.jsx +854 -0
- package/lib/estudio/kit.js +5 -1
- package/lib/estudio/kokoro.js +18 -4
- package/lib/estudio/margem.js +1 -1
- package/lib/estudio/mundo.js +305 -0
- package/lib/estudio/personagem.js +234 -10
- package/lib/estudio/producao.js +107 -17
- package/lib/estudio/projeto.js +77 -22
- package/lib/estudio/remotion.js +2 -0
- package/lib/leitura.js +176 -0
- package/lib/local/cerebro.json +180 -3
- package/lib/local/entrada.js +61 -4
- package/lib/mcp.js +296 -12
- package/lib/memoria-conversa.js +32 -3
- package/lib/mencoes.js +5 -1
- package/lib/nome-curto.js +126 -0
- package/lib/nome-projeto.js +5 -2
- package/lib/outra-pasta.js +161 -0
- package/lib/perigo.js +34 -2
- package/lib/referencia.js +40 -11
- package/lib/repeticao.js +20 -1
- package/lib/tela.js +1 -1
- package/lib/tools.js +76 -6
- package/lib/travado.js +141 -0
- package/lib/ui.js +1 -0
- package/package.json +2 -2
package/lib/estudio/kit.js
CHANGED
|
@@ -61,9 +61,13 @@ const PECAS = {
|
|
|
61
61
|
fluido: { precisa: ['partitura'] },
|
|
62
62
|
// O vídeo gravado em 3D (kit/personagem.jsx): o set, os personagens e a câmera (personagem.js).
|
|
63
63
|
personagem: { precisa: ['partitura'] },
|
|
64
|
+
// O mesmo vídeo gravado, no visual de blocos da referência (kit/voxel.jsx): o padrão desde a 10.5.
|
|
65
|
+
voxel: { precisa: ['partitura'] },
|
|
66
|
+
// A animação pronta da biblioteca (kit/animacao.jsx, colecao.js): o Lottie tocado quadro a quadro.
|
|
67
|
+
animacao: { precisa: ['src'] },
|
|
64
68
|
};
|
|
65
69
|
|
|
66
|
-
const ARQUIVOS = ['base.jsx', 'vox.jsx', 'marca.jsx', 'tres.jsx', 'gerativo.jsx', 'fluido.jsx', 'personagem.jsx', 'index.jsx'];
|
|
70
|
+
const ARQUIVOS = ['base.jsx', 'vox.jsx', 'marca.jsx', 'tres.jsx', 'gerativo.jsx', 'fluido.jsx', 'personagem.jsx', 'mundo.jsx', 'voxel.jsx', 'animacao.jsx', 'index.jsx'];
|
|
67
71
|
|
|
68
72
|
/** Os arquivos do kit, para o projeto Remotion: [{nome: 'kit/base.jsx', conteudo}]. */
|
|
69
73
|
function arquivos() {
|
package/lib/estudio/kokoro.js
CHANGED
|
@@ -87,11 +87,25 @@ async function narrar(lista, mp3, { voz, uv, espeak = acharEspeak(), executar =
|
|
|
87
87
|
}
|
|
88
88
|
}
|
|
89
89
|
|
|
90
|
-
/** A ordem das vozes: pedida → Kokoro e depois o Edge; senão Gemini →
|
|
91
|
-
|
|
90
|
+
/** A ordem das vozes: pedida → Kokoro e depois o Edge; senão Gemini → Edge → Kokoro.
|
|
91
|
+
O Edge (vozes neurais da Microsoft, ou a ElevenLabs quando há chave) vem
|
|
92
|
+
ANTES do Kokoro desde 11/10: com a cota do Gemini no fim, o Kokoro em
|
|
93
|
+
português era a "voz robótica" que a pessoa ouvia. `motorVoz: 'edge'` no
|
|
94
|
+
roteiro pula as outras. */
|
|
92
95
|
function ordem(voz, env = process.env, { soEdge = false } = {}) {
|
|
93
96
|
if (soEdge) return ['edge'];
|
|
94
|
-
return querKokoro(voz, env) ? ['kokoro', 'edge'] : ['gemini', '
|
|
97
|
+
return querKokoro(voz, env) ? ['kokoro', 'edge'] : ['gemini', 'edge', 'kokoro'];
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/* A voz do Gemini no Edge: o narrar.py só conhece antonio, francisca e
|
|
101
|
+
thalita — o nome do Gemini ("Kore") caía na primeira, e toda fala de mulher
|
|
102
|
+
saía na voz do Antonio. Mantém o GÊNERO de quem fala. */
|
|
103
|
+
const GEMINI_FEMININAS = new Set(['kore', 'aoede', 'leda', 'zephyr', 'sulafat', 'achird', 'despina', 'erinome', 'laomedeia', 'pulcherrima', 'vindemiatrix', 'callirrhoe', 'autonoe', 'gacrux']);
|
|
104
|
+
function vozNoEdge(voz, reserva = 'thalita') {
|
|
105
|
+
const v = String(voz || '').trim().toLowerCase();
|
|
106
|
+
if (['antonio', 'francisca', 'thalita'].includes(v)) return v;
|
|
107
|
+
if (!v || querKokoro(v, {})) return reserva;
|
|
108
|
+
return GEMINI_FEMININAS.has(v) ? 'francisca' : 'antonio';
|
|
95
109
|
}
|
|
96
110
|
|
|
97
111
|
/** Roda a corrente: a primeira voz que der certo ganha; a falha de uma nunca derruba. */
|
|
@@ -108,4 +122,4 @@ async function corrente(passos, provedores) {
|
|
|
108
122
|
return ultimo;
|
|
109
123
|
}
|
|
110
124
|
|
|
111
|
-
module.exports = { querKokoro, pronuncia, vozKokoro, acharEspeak, comando, narrar, ordem, corrente, DEPS, SCRIPT };
|
|
125
|
+
module.exports = { vozNoEdge, querKokoro, pronuncia, vozKokoro, acharEspeak, comando, narrar, ordem, corrente, DEPS, SCRIPT };
|
package/lib/estudio/margem.js
CHANGED
|
@@ -44,7 +44,7 @@ function areaSegura(W, H, m = MARGEM) {
|
|
|
44
44
|
/* As peças do kit que são o QUADRO inteiro (textura, padrão, o gerativo que
|
|
45
45
|
tem a própria conta) ou enfeite de fundo. */
|
|
46
46
|
const PECAS_FUNDO = new Set(['textura', 'padrao', 'particulas', 'papel', 'grade', 'orbe', 'cromo', 'esfera', 'estrela',
|
|
47
|
-
'gerativo', 'fluido', 'personagem', 'site']);
|
|
47
|
+
'gerativo', 'fluido', 'personagem', 'voxel', 'site']);
|
|
48
48
|
|
|
49
49
|
const num = (v) => (typeof v === 'number' && Number.isFinite(v) ? v : null);
|
|
50
50
|
|
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* mundo.js — o lugar, os bichos e as coisas do vídeo 3D, a partir do PEDIDO.
|
|
3
|
+
*
|
|
4
|
+
* "não quero que ele só troque as falas e os textos ... se eu pedir dois
|
|
5
|
+
* humanos ele vai fazer dois humanos, se eu pedir um cachorro ele vai fazer,
|
|
6
|
+
* se eu pedir um humano na praia ele vai fazer — vai mudar o cenário, vai
|
|
7
|
+
* mudar as coisas, e não é coisa pré-definida" (Joel, 11/10)
|
|
8
|
+
*
|
|
9
|
+
* Até a 10.5 o 3D tinha UM set (a parede de telas) e o elenco era gente ou o
|
|
10
|
+
* mascote. Aqui o set vira um MUNDO montado de peças:
|
|
11
|
+
*
|
|
12
|
+
* · o LUGAR (praia, cidade, floresta, espaço, cozinha…): céu, chão, água,
|
|
13
|
+
* luz e hora do dia; vem do texto livre ("numa praia ao pôr do sol");
|
|
14
|
+
* · os ELEMENTOS: o que há no lugar. Uma palavra do catálogo (palmeira, carro,
|
|
15
|
+
* prédio, foguete…) vira uma peça de blocos desenhada no kit/mundo.jsx; uma
|
|
16
|
+
* palavra FORA do catálogo vira um ícone 3D da biblioteca (colecao.js,
|
|
17
|
+
* 1.595 objetos) transformado em blocos na hora do render — é isso que
|
|
18
|
+
* tira o "pré-definido": qualquer coisa que tenha ícone entra na cena;
|
|
19
|
+
* · o ELENCO: pessoa, robô/mascote, BICHO (cachorro, gato, cavalo, vaca,
|
|
20
|
+
* pinguim, peixe, dinossauro… — o kit/mundo.jsx monta cada espécie) ou
|
|
21
|
+
* COISA (um ícone em blocos que fala: "uma pizza conversando com um café").
|
|
22
|
+
* E os FIGURANTES: quem está na cena sem falar ("um humano com o cachorro").
|
|
23
|
+
*
|
|
24
|
+
* A IA decide (roteiro.cena, roteiro.personagens, parte.cenario); o que ela
|
|
25
|
+
* não disse sai do texto do pedido e das falas — a mesma regra do resto do
|
|
26
|
+
* Estúdio: o modelo manda, a leitura do texto só completa. É o que faz um
|
|
27
|
+
* modelo local pequeno, que só escreve as falas, ainda ganhar a praia que a
|
|
28
|
+
* pessoa pediu.
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
'use strict';
|
|
32
|
+
|
|
33
|
+
const sem = (s) => String(s || '').toLowerCase().normalize('NFD').replace(/[̀-ͯ]/g, '');
|
|
34
|
+
|
|
35
|
+
/* ── OS LUGARES ─────────────────────────────────────────────────────────
|
|
36
|
+
ceu: [alto, horizonte] · chao: {tipo, cor, cor2} · agua: cor do mar ·
|
|
37
|
+
dentro: set fechado (parede no fundo) · props: o que o lugar tem quando a
|
|
38
|
+
cena não disse. As palavras são lidas sem acento. */
|
|
39
|
+
const LUGARES = {
|
|
40
|
+
/* A COBERTURA À NOITE (2ª referência do Joel, 11/10): a janela de vidro
|
|
41
|
+
com a cidade acesa lá fora, as luzes grandes desfocadas (bokeh), o
|
|
42
|
+
parapeito escuro e os CARTÕES de interface flutuando na frente (o
|
|
43
|
+
assunto do vídeo, desfocados pela lente). É o set padrão quando o
|
|
44
|
+
pedido não diz lugar. */
|
|
45
|
+
cobertura: { palavras: /\b(cobertura|terraco|rooftop|janela|vista da cidade|apartamento alto|predio alto|escritorio a noite)\b/, dentro: true, cobertura: true,
|
|
46
|
+
parede: '#050816', chao: { tipo: 'madeira', cor: '#2a1f1a', cor2: '#241a16' }, luz: '#ffd2b0', recorte: '#6f8bff', acento: '#ff6a2b', props: [] },
|
|
47
|
+
telas: { palavras: /\b(estudio|podcast|tecnologia|telas?|set|programa|live|transmissao)\b/, dentro: true, telas: true,
|
|
48
|
+
parede: '#07061a', chao: { tipo: 'ladrilho', cor: '#4338ca', cor2: '#3b32b8' }, luz: '#ffd2b0', recorte: '#8b7bff', acento: '#ff7a45', props: [] },
|
|
49
|
+
praia: { palavras: /\b(praia|mar|litoral|beira[ -]mar|areia|orla|surf\w*|ilha)\b/, ceu: ['#3d8fe0', '#c4e8ff'], agua: '#1d8bc9',
|
|
50
|
+
chao: { tipo: 'areia', cor: '#ead29c', cor2: '#dfc48a' }, luz: '#fff2d6', recorte: '#ffd08a', acento: '#ff7a45', sol: true,
|
|
51
|
+
props: ['palmeira', 'palmeira', 'guarda-sol', 'cadeira de praia', 'palmeira', 'castelo de areia', 'pedra'] },
|
|
52
|
+
cidade: { palavras: /\b(cidade|rua|avenida|centro|predios?|esquina|calcada|metropole|urbano)\b/, ceu: ['#5b9be6', '#dbeaff'],
|
|
53
|
+
chao: { tipo: 'asfalto', cor: '#5b5e67', cor2: '#53565f' }, luz: '#fff1dc', recorte: '#9fc3ff', acento: '#facc15', sol: true,
|
|
54
|
+
props: ['predio', 'predio', 'predio', 'poste', 'carro', 'arvore', 'predio', 'poste', 'carro', 'predio'] },
|
|
55
|
+
floresta: { palavras: /\b(floresta|mata|selva|bosque|trilha|acampamento|amazonia)\b/, ceu: ['#6fb3e8', '#dcf2d6'],
|
|
56
|
+
chao: { tipo: 'grama', cor: '#4f9a3c', cor2: '#468c35' }, luz: '#fff0d0', recorte: '#b6f0a0', acento: '#facc15', sol: true, neblina: 0.6,
|
|
57
|
+
props: ['arvore', 'pinheiro', 'arvore', 'pinheiro', 'arbusto', 'pedra', 'arvore', 'pinheiro', 'cogumelo', 'arvore'] },
|
|
58
|
+
parque: { palavras: /\b(parque|jardim|praca|gramado|piquenique)\b/, ceu: ['#58a6ee', '#d9eeff'],
|
|
59
|
+
chao: { tipo: 'grama', cor: '#5aa845', cor2: '#4f9a3c' }, luz: '#fff2d6', recorte: '#c8f0b0', acento: '#f472b6', sol: true,
|
|
60
|
+
props: ['arvore', 'banco', 'flor', 'arvore', 'poste', 'flor', 'arbusto', 'arvore', 'flor'] },
|
|
61
|
+
fazenda: { palavras: /\b(fazenda|sitio|roca|interior|celeiro|curral|plantacao|colheita)\b/, ceu: ['#68aef0', '#f3ecd0'],
|
|
62
|
+
chao: { tipo: 'grama', cor: '#6aa84f', cor2: '#5e9a44' }, luz: '#fff1d0', recorte: '#ffe0a0', acento: '#dc2626', sol: true,
|
|
63
|
+
props: ['celeiro', 'cerca', 'cerca', 'arvore', 'feno', 'cerca', 'arvore', 'feno'] },
|
|
64
|
+
neve: { palavras: /\b(neve|inverno|gelo|polo|nevando|esqui|frio)\b/, ceu: ['#9fbfe6', '#eef4fb'],
|
|
65
|
+
chao: { tipo: 'neve', cor: '#f4f8fc', cor2: '#e6eef6' }, luz: '#eef4ff', recorte: '#a8c8ff', acento: '#38bdf8', sol: true,
|
|
66
|
+
props: ['pinheiro nevado', 'pinheiro nevado', 'boneco de neve', 'montanha', 'pinheiro nevado', 'pedra', 'pinheiro nevado'] },
|
|
67
|
+
deserto: { palavras: /\b(deserto|duna|sertao|saara|oasis)\b/, ceu: ['#f0a35a', '#ffe2ae'],
|
|
68
|
+
chao: { tipo: 'areia', cor: '#e2b06c', cor2: '#d6a05c' }, luz: '#ffe6c0', recorte: '#ffb070', acento: '#ea580c', sol: true,
|
|
69
|
+
props: ['cacto', 'cacto', 'pedra', 'montanha', 'cacto', 'pedra'] },
|
|
70
|
+
espaco: { palavras: /\b(espaco|lua|planeta|galaxia|foguete|astronauta|marte|orbita|estrelas|universo|nave)\b/, ceu: ['#02020f', '#0d0b2c'], estrelas: true,
|
|
71
|
+
chao: { tipo: 'lua', cor: '#a1a1ad', cor2: '#90909c' }, luz: '#e8ecff', recorte: '#8b7bff', acento: '#a78bfa',
|
|
72
|
+
props: ['planeta', 'foguete', 'pedra', 'planeta', 'cratera', 'pedra'] },
|
|
73
|
+
fundo_do_mar: { palavras: /\b(fundo do mar|submarino|oceano|aquario|mergulh\w*|recife|coral)\b/, ceu: ['#022c46', '#0b6f94'], subaquatico: true,
|
|
74
|
+
chao: { tipo: 'areia', cor: '#c9b98a', cor2: '#bba97a' }, luz: '#bfefff', recorte: '#38bdf8', acento: '#f97316', neblina: 1,
|
|
75
|
+
props: ['alga', 'coral', 'pedra', 'alga', 'coral', 'bau', 'alga'] },
|
|
76
|
+
escritorio: { palavras: /\b(escritorio|trabalho|reuniao|empresa|firma|coworking|startup)\b/, dentro: true, parede: '#d8d0c2',
|
|
77
|
+
chao: { tipo: 'madeira', cor: '#9a7350', cor2: '#8d6846' }, luz: '#fff1dc', recorte: '#ffe2b8', acento: '#2563eb',
|
|
78
|
+
props: ['mesa', 'computador', 'planta', 'estante', 'cadeira', 'quadro'] },
|
|
79
|
+
sala: { palavras: /\b(sala|casa|sofa|quarto|lar|apartamento|sala de estar)\b/, dentro: true, parede: '#e6d6c3',
|
|
80
|
+
chao: { tipo: 'madeira', cor: '#a87b52', cor2: '#9a6e47' }, luz: '#ffe8c8', recorte: '#ffd6a0', acento: '#e11d48',
|
|
81
|
+
props: ['sofa', 'luminaria', 'planta', 'estante', 'tapete', 'quadro'] },
|
|
82
|
+
cozinha: { palavras: /\b(cozinha|restaurante|padaria|cafeteria|cafe|lanchonete|confeitaria|pizzaria|bar)\b/, dentro: true, parede: '#efe4d2',
|
|
83
|
+
chao: { tipo: 'ladrilho', cor: '#e8e1d4', cor2: '#cfc5b4' }, luz: '#fff0d6', recorte: '#ffd9a0', acento: '#dc2626',
|
|
84
|
+
props: ['balcao', 'mesa', 'cadeira', 'planta', 'estante', 'luminaria'] },
|
|
85
|
+
escola: { palavras: /\b(escola|sala de aula|aula|professor|faculdade|universidade|colegio)\b/, dentro: true, parede: '#dfe8dc',
|
|
86
|
+
chao: { tipo: 'madeira', cor: '#b08458', cor2: '#a3794f' }, luz: '#fffaf0', recorte: '#d6f0ff', acento: '#16a34a',
|
|
87
|
+
props: ['lousa', 'mesa', 'mesa', 'estante', 'planta'] },
|
|
88
|
+
palco: { palavras: /\b(palco|show|teatro|concerto|festa|balada|evento)\b/, dentro: true, parede: '#0b0612',
|
|
89
|
+
chao: { tipo: 'madeira', cor: '#3a2a20', cor2: '#33251c' }, luz: '#ffe1f5', recorte: '#ff4fd8', acento: '#22d3ee', holofotes: true,
|
|
90
|
+
props: ['caixa de som', 'caixa de som', 'luminaria'] },
|
|
91
|
+
campo: { palavras: /\b(campo|montanhas?|colina|natureza|ao ar livre|vale)\b/, ceu: ['#5aa3ec', '#dcefff'],
|
|
92
|
+
chao: { tipo: 'grama', cor: '#5ca846', cor2: '#529b3e' }, luz: '#fff2d6', recorte: '#c8f0b0', acento: '#facc15', sol: true,
|
|
93
|
+
props: ['montanha', 'arvore', 'flor', 'arvore', 'pedra', 'montanha', 'flor'] },
|
|
94
|
+
};
|
|
95
|
+
// O que o set antigo chamava por outro nome.
|
|
96
|
+
const APELIDOS_LUGAR = { neon: 'palco', oceano: 'fundo_do_mar', terra: 'deserto', podcast: 'telas', estudio: 'telas', noite: 'telas' };
|
|
97
|
+
|
|
98
|
+
const HORAS = {
|
|
99
|
+
noite: { palavras: /\b(noite|noturn\w*|madrugada|luar|a noite|de noite)\b/, ceu: ['#070b24', '#1c2552'], luz: '#b8c6ff', forca: 0.45, lua: true, estrelas: true },
|
|
100
|
+
por_do_sol: { palavras: /\b(por do sol|entardecer|fim de tarde|crepusculo|poente|anoitecer)\b/, ceu: ['#46368a', '#ff9a5a'], luz: '#ffb27a', forca: 0.8, solBaixo: true },
|
|
101
|
+
amanhecer: { palavras: /\b(amanhecer|nascer do sol|alvorada|de manha cedo)\b/, ceu: ['#6a8fd8', '#ffd2a8'], luz: '#ffd9b0', forca: 0.85, solBaixo: true },
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
/* ── O CATÁLOGO DE PEÇAS (kit/mundo.jsx desenha cada uma) ───────────────
|
|
105
|
+
A chave é a peça; as palavras são como a pessoa (ou a IA) diz. */
|
|
106
|
+
const PECAS = {
|
|
107
|
+
palmeira: /\bpalmeiras?|coqueiros?\b/, arvore: /\barvores?|arvorezinha\b/, pinheiro: /\bpinheiros?\b/, 'pinheiro nevado': /\bpinheiros? nevados?\b/,
|
|
108
|
+
arbusto: /\barbustos?|moitas?\b/, flor: /\bflor(es)?\b/, pedra: /\bpedras?|rochas?\b/, cacto: /\bcactos?\b/, montanha: /\bmontanhas?|morros?\b/,
|
|
109
|
+
casa: /\bcasas?|casinha\b/, predio: /\bpredios?|edificios?|arranha[- ]ceus?\b/, celeiro: /\bceleiros?\b/, poste: /\bpostes?\b/,
|
|
110
|
+
carro: /\bcarros?|automove(l|is)\b/, 'guarda-sol': /\bguarda[- ]sois?|guarda[- ]sol\b/, 'cadeira de praia': /\bcadeiras? de praia\b/,
|
|
111
|
+
'castelo de areia': /\bcastelos? de areia\b/, mesa: /\bmesas?\b/, cadeira: /\bcadeiras?\b/, sofa: /\bsofas?\b/, planta: /\bplantas?|vasos?\b/,
|
|
112
|
+
estante: /\bestantes?|prateleiras?\b/, computador: /\bcomputador(es)?|notebooks?|pc\b/, luminaria: /\bluminarias?|abajur\b/,
|
|
113
|
+
quadro: /\bquadros?\b/, lousa: /\blousas?|quadro negro\b/, balcao: /\bbalca(o|oes)\b/, tapete: /\btapetes?\b/, banco: /\bbancos? de praca|banquinhos?\b/,
|
|
114
|
+
cerca: /\bcercas?\b/, feno: /\bfenos?\b/, 'boneco de neve': /\bbonecos? de neve\b/, foguete: /\bfoguetes?\b/, planeta: /\bplanetas?\b/,
|
|
115
|
+
cratera: /\bcrateras?\b/, alga: /\balgas?\b/, coral: /\bcorais?|coral\b/, bau: /\bbaus?|tesouros?\b/, cogumelo: /\bcogumelos?\b/,
|
|
116
|
+
nuvem: /\bnuvens?\b/, fogueira: /\bfogueiras?\b/, barraca: /\bbarracas?|tendas?\b/, barco: /\bbarcos?|canoas?\b/,
|
|
117
|
+
'caixa de som': /\bcaixas? de som|alto[- ]falantes?\b/,
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
/* ── AS ESPÉCIES (kit/mundo.jsx monta cada uma de blocos) ────────────── */
|
|
121
|
+
const ESPECIES = {
|
|
122
|
+
cachorro: /\b(cachorr\w*|cao|caes|cachorrinho|dog|vira[- ]lata|labrador|filhote)\b/, gato: /\b(gat[oa]s?|gatinh\w*|felino)\b/,
|
|
123
|
+
cavalo: /\b(caval\w*|egua|potro|pônei|ponei)\b/, vaca: /\b(vacas?|boi|bois|touro|bezerro)\b/, porco: /\b(porc\w*|leitao)\b/,
|
|
124
|
+
ovelha: /\b(ovelhas?|carneiros?|cordeiro)\b/, raposa: /\b(raposas?)\b/, lobo: /\b(lob[oa]s?)\b/, urso: /\b(urs[oa]s?)\b/,
|
|
125
|
+
panda: /\b(pandas?)\b/, coelho: /\b(coelh\w*)\b/, leao: /\b(leao|leoes|leoa)\b/, tigre: /\b(tigres?)\b/, elefante: /\b(elefantes?)\b/,
|
|
126
|
+
girafa: /\b(girafas?)\b/, galinha: /\b(galinhas?|galos?|pintinhos?)\b/, pato: /\b(pat[oa]s?|patinho)\b/, pinguim: /\b(pinguins?)\b/,
|
|
127
|
+
passaro: /\b(passar\w*|ave|aves|passarinho)\b/, papagaio: /\b(papagaios?|arara)\b/, coruja: /\b(corujas?)\b/,
|
|
128
|
+
peixe: /\b(peixes?|peixinho)\b/, tubarao: /\b(tubara(o|oes))\b/, golfinho: /\b(golfinhos?)\b/, tartaruga: /\b(tartarugas?|jabuti)\b/,
|
|
129
|
+
dinossauro: /\b(dinossauros?|dino|t[- ]?rex)\b/, sapo: /\b(sapos?|ra)\b/, macaco: /\b(macac\w*)\b/,
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
const PESSOA = /\b(humanos?|pessoas?|homens?|mulher(es)?|menin[oa]s?|crianc\w*|amig[oa]s?|garot[oa]s?|rapaz|mo[cç][oa]s?|senhor(a|es)?|vov[oóô]|av[oóô]s?|casal|familia|cliente|vendedor\w*|professor\w*|alun[oa]s?|medic[oa]s?|astronautas?|surfistas?|chef|cozinheir[oa]s?)\b/;
|
|
133
|
+
const ROBO = /\b(robo|robos|ia|inteligencia artificial|mascote|claude|chatgpt|gpt|gemini|assistente|bot)\b/;
|
|
134
|
+
const NUMEROS = { um: 1, uma: 1, dois: 2, duas: 2, tres: 3, quatro: 4, cinco: 5, casal: 2, par: 2 };
|
|
135
|
+
|
|
136
|
+
function contar(texto, re) {
|
|
137
|
+
// "dois humanos", "3 cachorros", "um casal": o número logo antes da palavra.
|
|
138
|
+
const s = sem(texto);
|
|
139
|
+
if (re === PESSOA && /\b(um )?casal\b/.test(s)) return 2;
|
|
140
|
+
const m = new RegExp(`(\\d+|um|uma|dois|duas|tres|quatro|cinco)\\s+(?:\\w+\\s+)?${re.source.replace(/^\\b|\\b$/g, '')}`).exec(s);
|
|
141
|
+
if (m) return Math.min(4, Number(m[1]) || NUMEROS[m[1]] || 1);
|
|
142
|
+
return re.test(s) ? 1 : 0;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/** O lugar e a hora que o texto pede, ou null. */
|
|
146
|
+
function lerLugar(texto) {
|
|
147
|
+
const s = sem(texto);
|
|
148
|
+
let lugar = null, onde = -1;
|
|
149
|
+
// O mais específico primeiro: "fundo do mar" antes de "mar", "sala de aula" antes de "sala".
|
|
150
|
+
const ordem = ['cobertura', 'fundo_do_mar', 'escola', 'praia', 'espaco', 'neve', 'deserto', 'fazenda', 'floresta', 'parque', 'cidade', 'cozinha', 'escritorio', 'palco', 'sala', 'campo', 'telas'];
|
|
151
|
+
for (const n of ordem) { const m = LUGARES[n].palavras.exec(s); if (m) { lugar = n; onde = m.index; break; } }
|
|
152
|
+
/* A hora vale para AQUELE lugar: "na praia, depois na cidade à noite" é
|
|
153
|
+
praia de dia. Com lugar no texto, só a hora a até 40 letras dele. */
|
|
154
|
+
let hora = null;
|
|
155
|
+
for (const [n, h] of Object.entries(HORAS)) {
|
|
156
|
+
const m = h.palavras.exec(s);
|
|
157
|
+
if (!m) continue;
|
|
158
|
+
if (onde < 0) { hora = n; break; }
|
|
159
|
+
// Outro lugar no meio ("praia, depois vão pra cidade à noite"): a hora é DELE.
|
|
160
|
+
const entre = s.slice(Math.min(m.index, onde) + 1, Math.max(m.index, onde));
|
|
161
|
+
const outroLugar = Object.entries(LUGARES).some(([k, L]) => k !== lugar && L.palavras.test(entre));
|
|
162
|
+
if (!outroLugar && Math.abs(m.index - onde) <= 40) { hora = n; break; }
|
|
163
|
+
}
|
|
164
|
+
return { lugar, hora };
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/** As peças do catálogo e os bichos que o texto cita (em ordem de aparição). */
|
|
168
|
+
function lerCoisas(texto) {
|
|
169
|
+
const s = sem(texto);
|
|
170
|
+
const pecas = [];
|
|
171
|
+
for (const [nome, re] of Object.entries(PECAS)) {
|
|
172
|
+
const m = re.exec(s);
|
|
173
|
+
if (m) pecas.push({ nome, em: m.index });
|
|
174
|
+
}
|
|
175
|
+
// "pinheiro nevado" ganha de "pinheiro"; "cadeira de praia" ganha de "cadeira".
|
|
176
|
+
const filtradas = pecas.filter((p) => !pecas.some((q) => q !== p && q.nome.startsWith(p.nome + ' ') && Math.abs(q.em - p.em) < 3)
|
|
177
|
+
&& !pecas.some((q) => q !== p && q.nome.includes(p.nome) && q.nome !== p.nome && Math.abs(q.em - p.em) < 12));
|
|
178
|
+
const bichos = [];
|
|
179
|
+
for (const [esp, re] of Object.entries(ESPECIES)) {
|
|
180
|
+
const m = re.exec(s);
|
|
181
|
+
if (m) bichos.push({ especie: esp, em: m.index, quantos: contar(texto, re) || 1 });
|
|
182
|
+
}
|
|
183
|
+
return { pecas: filtradas.sort((a, b) => a.em - b.em).map((p) => p.nome), bichos: bichos.sort((a, b) => a.em - b.em) };
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/** O que o PEDIDO descreve do elenco: quantas pessoas, que bichos, se há IA. */
|
|
187
|
+
function lerElenco(texto) {
|
|
188
|
+
const s = sem(texto);
|
|
189
|
+
const { bichos } = lerCoisas(texto);
|
|
190
|
+
return { pessoas: contar(texto, PESSOA), robo: ROBO.test(s), bichos };
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/* ── O MUNDO MONTADO ────────────────────────────────────────────────────
|
|
194
|
+
A peça é posta numa faixa ATRÁS e DOS LADOS do elenco, nunca no corredor
|
|
195
|
+
entre a câmera e quem fala. Com mar, a faixa é a areia antes da água. */
|
|
196
|
+
function rngDe(semente) {
|
|
197
|
+
let s = (Number(semente) >>> 0) || 1;
|
|
198
|
+
return () => { s = (Math.imul(s ^ (s >>> 15), 2246822507) + 0x9e3779b9) >>> 0; return s / 4294967296; };
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const ehCor = (c) => typeof c === 'string' && /^#[0-9a-f]{6}$/i.test(c);
|
|
202
|
+
|
|
203
|
+
function posicionar(nomes, { semente = 1, agua = false, dentro = false, largura = 1 } = {}) {
|
|
204
|
+
const r = rngDe(semente);
|
|
205
|
+
const out = [];
|
|
206
|
+
const livre = (x, z, raio) => out.every((p) => Math.hypot(p.x - x, p.z - z) > raio + (p.raio || 0.8));
|
|
207
|
+
nomes.forEach((nome, i) => {
|
|
208
|
+
const grande = /predio|montanha|celeiro|planeta|foguete|palmeira|pinheiro|arvore/.test(typeof nome === 'string' ? nome : '');
|
|
209
|
+
const raio = grande ? 1.3 : 0.75;
|
|
210
|
+
for (let tent = 0; tent < 40; tent++) {
|
|
211
|
+
let x, z;
|
|
212
|
+
if (dentro) {
|
|
213
|
+
// Encostado na parede do fundo ou nos lados do set.
|
|
214
|
+
x = (r() * 2 - 1) * 4.6 * largura; z = -3.1 + r() * 0.9;
|
|
215
|
+
if (Math.abs(x) < 1.7 && tent < 30) continue;
|
|
216
|
+
} else if (agua) {
|
|
217
|
+
const lado = i % 2 ? 1 : -1;
|
|
218
|
+
x = lado * (1.9 + r() * 4.5) * largura; z = -4.2 + r() * 3.6;
|
|
219
|
+
} else {
|
|
220
|
+
const lado = i % 2 ? 1 : -1;
|
|
221
|
+
const longe = grande ? 0.55 + r() * 0.45 : r();
|
|
222
|
+
x = lado * (1.8 + r() * 5.5) * largura; z = -1.6 - longe * 9;
|
|
223
|
+
if (r() < 0.25 && !grande) { x = lado * (2.2 + r() * 1.5); z = -0.4 + r() * 1.4; }
|
|
224
|
+
}
|
|
225
|
+
if (!livre(x, z, raio) && tent < 39) continue;
|
|
226
|
+
out.push({ x: Math.round(x * 100) / 100, z: Math.round(z * 100) / 100, raio, giro: Math.round((r() * 2 - 1) * 60) / 100, escala: Math.round((0.85 + r() * 0.35) * 100) / 100 });
|
|
227
|
+
break;
|
|
228
|
+
}
|
|
229
|
+
});
|
|
230
|
+
return out;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* O mundo de um trecho do vídeo.
|
|
235
|
+
* @param {string|object} pedido "praia ao pôr do sol" ou {lugar, hora, elementos, ceu, chao, ...}
|
|
236
|
+
* @param {object} op {texto (o pedido e as falas: completa o lugar), pedidoTexto (só o
|
|
237
|
+
* pedido da pessoa: as coisas que ele cita entram na cena), semente, nElenco}
|
|
238
|
+
*/
|
|
239
|
+
function mundoDe(pedido, { texto = '', pedidoTexto = '', semente = 1, nElenco = 1 } = {}) {
|
|
240
|
+
const obj = pedido && typeof pedido === 'object' ? pedido : { lugar: String(pedido || '') };
|
|
241
|
+
const descr = [obj.lugar, obj.descricao, obj.hora].filter(Boolean).join(' ');
|
|
242
|
+
let { lugar, hora } = lerLugar(descr);
|
|
243
|
+
const nomeDireto = sem(obj.lugar).replace(/\s+/g, '_');
|
|
244
|
+
if (!lugar && LUGARES[nomeDireto]) lugar = nomeDireto;
|
|
245
|
+
if (!lugar && APELIDOS_LUGAR[nomeDireto]) lugar = APELIDOS_LUGAR[nomeDireto];
|
|
246
|
+
// Sem lugar no pedido da cena, o texto do vídeo (o pedido da pessoa e as falas) completa.
|
|
247
|
+
const doTexto = lerLugar(texto);
|
|
248
|
+
if (!lugar) lugar = doTexto.lugar || 'cobertura';
|
|
249
|
+
if (!hora) hora = doTexto.hora;
|
|
250
|
+
if (APELIDOS_LUGAR[lugar]) lugar = APELIDOS_LUGAR[lugar];
|
|
251
|
+
const L = LUGARES[lugar];
|
|
252
|
+
const H = hora && !L.dentro ? HORAS[hora] : null;
|
|
253
|
+
|
|
254
|
+
// Os elementos: os que a cena listou (qualquer palavra) + os que o texto cita + os do lugar.
|
|
255
|
+
const pedidos = Array.isArray(obj.elementos) ? obj.elementos.map((e) => (typeof e === 'string' ? e : (e && (e.nome || e.tipo)) || '')).filter(Boolean) : [];
|
|
256
|
+
// As falas citam coisa de passagem ("comprei um carro"): só o PEDIDO põe coisa na cena.
|
|
257
|
+
const citados = lerCoisas([descr, pedidoTexto, Array.isArray(obj.elementos) ? '' : String(obj.elementos || '')].join(' ')).pecas;
|
|
258
|
+
const nomes = [];
|
|
259
|
+
for (const e of [...pedidos, ...citados]) {
|
|
260
|
+
const s = sem(e).trim();
|
|
261
|
+
const peca = Object.keys(PECAS).find((k) => PECAS[k].test(s) || sem(k) === s);
|
|
262
|
+
nomes.push(peca ? { peca } : { icone: s.slice(0, 40) });
|
|
263
|
+
}
|
|
264
|
+
// O lugar completa até 7 peças: a cena nunca fica vazia, e o que a IA pediu vem primeiro.
|
|
265
|
+
for (const p of L.props) if (nomes.length < 7 || nomes.length < L.props.length) nomes.push({ peca: p });
|
|
266
|
+
const lugares = posicionar(nomes.map((n) => n.peca || n.icone), { semente, agua: !!L.agua, dentro: !!L.dentro, largura: nElenco > 2 ? 1.25 : 1 });
|
|
267
|
+
const props = nomes.slice(0, lugares.length).map((n, i) => ({ ...n, ...lugares[i] }));
|
|
268
|
+
|
|
269
|
+
const ceu = Array.isArray(obj.ceu) && obj.ceu.every(ehCor) ? obj.ceu : (H ? H.ceu : L.ceu) || null;
|
|
270
|
+
const chao = { ...L.chao, ...(obj.chao && typeof obj.chao === 'object' ? obj.chao : {}), ...(ehCor(obj.chao) ? { cor: obj.chao, cor2: obj.chao } : {}) };
|
|
271
|
+
return {
|
|
272
|
+
nome: lugar + (H ? ':' + hora : ''), lugar, hora: H ? hora : null,
|
|
273
|
+
dentro: !!L.dentro, telas: !!L.telas, cobertura: !!L.cobertura, parede: ehCor(obj.parede) ? obj.parede : L.parede || null,
|
|
274
|
+
ceu, chao, agua: L.agua || null, subaquatico: !!L.subaquatico, holofotes: !!L.holofotes,
|
|
275
|
+
sol: !!(L.sol && !(H && H.lua)), solBaixo: !!(H && H.solBaixo), lua: !!(H && H.lua), estrelas: !!(L.estrelas || (H && H.estrelas)),
|
|
276
|
+
neblina: L.neblina || 0,
|
|
277
|
+
luz: ehCor(obj.luz) ? obj.luz : (H ? H.luz : L.luz), forca: H ? H.forca : 1,
|
|
278
|
+
recorte: L.recorte, acento: ehCor(obj.acento) ? obj.acento : L.acento,
|
|
279
|
+
props,
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/** Os ícones que o mundo e o elenco precisam baixar (colecao.js) antes do render. */
|
|
284
|
+
function iconesPendentes(partitura) {
|
|
285
|
+
const out = new Set();
|
|
286
|
+
for (const m of partitura.mundos || []) for (const p of m.props) if (p.icone && !p.src) out.add(p.icone);
|
|
287
|
+
for (const e of [...(partitura.elenco || []), ...(partitura.figurantes || [])]) if (e.tipo === 'coisa' && !e.src) out.add(e.icone);
|
|
288
|
+
return [...out];
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/** Põe o PNG baixado em cada peça e personagem que pediu aquele ícone; o que não achou ícone sai da cena. */
|
|
292
|
+
function aplicarIcones(partitura, achados) {
|
|
293
|
+
for (const m of partitura.mundos || []) {
|
|
294
|
+
m.props = m.props.filter((p) => !p.icone || achados[p.icone]).map((p) => (p.icone ? { ...p, src: achados[p.icone] } : p));
|
|
295
|
+
}
|
|
296
|
+
for (const e of [...(partitura.elenco || []), ...(partitura.figurantes || [])]) {
|
|
297
|
+
if (e.tipo === 'coisa') {
|
|
298
|
+
if (achados[e.icone]) e.src = achados[e.icone];
|
|
299
|
+
else { e.tipo = 'robo'; e.cor = e.cor || '#d97757'; } // sem ícone, a coisa que fala vira o mascote
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
return partitura;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
module.exports = { mundoDe, lerLugar, lerCoisas, lerElenco, iconesPendentes, aplicarIcones, LUGARES, HORAS, PECAS, ESPECIES, sem };
|