primocode 10.6.0 → 10.7.1
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 +58 -37
- package/lib/boas-vindas.js +12 -13
- package/lib/catalogo.js +5 -2
- package/lib/config.js +5 -2
- package/lib/conta.js +194 -169
- package/lib/estudio/cenarios.js +91 -0
- package/lib/estudio/colecao.js +1 -0
- package/lib/estudio/gerativo.js +25 -3
- package/lib/estudio/index.js +46 -5
- package/lib/estudio/kit/mundo.jsx +2 -2
- package/lib/estudio/kit/voxel.jsx +23 -7
- package/lib/estudio/movimentos.js +212 -0
- package/lib/estudio/mundo.js +25 -2
- package/lib/estudio/personagem.js +31 -5
- package/lib/estudio/plano.js +6 -0
- package/lib/nuvem.js +11 -22
- package/lib/planos.js +71 -0
- package/package.json +2 -2
package/lib/estudio/index.js
CHANGED
|
@@ -67,6 +67,8 @@ const lancamento = require('./lancamento.js');
|
|
|
67
67
|
const glifos = require('./glifos.js');
|
|
68
68
|
const personagem = require('./personagem.js');
|
|
69
69
|
const mundo = require('./mundo.js');
|
|
70
|
+
const cenarios = require('./cenarios.js');
|
|
71
|
+
const movimentos = require('./movimentos.js');
|
|
70
72
|
const colecao = require('./colecao.js');
|
|
71
73
|
const { escolherEstilo, LEVES } = require('./estilo.js');
|
|
72
74
|
const sonsLeves = require('./sons-leves.js');
|
|
@@ -277,6 +279,28 @@ function projetoRecente() {
|
|
|
277
279
|
return lista[0] || null;
|
|
278
280
|
}
|
|
279
281
|
|
|
282
|
+
/* Sem id, o projeto é o que acabou de ser criado. Visto em 12/10: o agente
|
|
283
|
+
criou o projeto, chamou estudio_narrar sem id e o vídeo morreu ali. Só vale
|
|
284
|
+
para chamada SEM id (id errado continua sendo erro) e para projeto de até
|
|
285
|
+
30 minutos, como no estudio_plano. */
|
|
286
|
+
function projetoOuRecente(id) {
|
|
287
|
+
if (id != null && String(id).trim() !== '') return projeto.ler(id);
|
|
288
|
+
return projetoRecente();
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/* roteiro.objetos: [{nome, src}] com o PNG DENTRO da pasta do projeto (o que
|
|
292
|
+
o estudio_midia baixou ou recortou). Caminho de fora some: o render só
|
|
293
|
+
enxerga a pasta do projeto, e a IA não escolhe arquivo da máquina. */
|
|
294
|
+
function objetosDoRoteiro(objetos, pasta) {
|
|
295
|
+
const raiz = path.resolve(pasta);
|
|
296
|
+
return (Array.isArray(objetos) ? objetos : []).map((o) => {
|
|
297
|
+
if (!o || typeof o.src !== 'string') return null;
|
|
298
|
+
const abs = path.resolve(raiz, o.src);
|
|
299
|
+
if (!abs.startsWith(raiz + path.sep) || !/\.png$/i.test(abs) || !fs.existsSync(abs)) return null;
|
|
300
|
+
return { nome: o.nome, src: path.relative(raiz, abs).replace(/\\/g, '/') };
|
|
301
|
+
}).filter(Boolean);
|
|
302
|
+
}
|
|
303
|
+
|
|
280
304
|
/** O roteiro é utilizável como veio? (objeto ou JSON, com partes de tipo conhecido) */
|
|
281
305
|
function roteiroValido(r) {
|
|
282
306
|
let x = r;
|
|
@@ -527,6 +551,19 @@ async function ferramentaMidia(args) {
|
|
|
527
551
|
/* A BIBLIOTECA (colecao.js, 11/10): o cardápio inteiro, o ícone 3D e a
|
|
528
552
|
animação pronta. Procurar não usa rede (o índice mora no pacote); com
|
|
529
553
|
id, baixa para o projeto e devolve o elemento pronto. */
|
|
554
|
+
/* OS MOVIMENTOS (movimentos.js, 12/10): 100+ entradas, saídas, ênfases e loops com nome. */
|
|
555
|
+
if (acao === 'movimentos' || acao === 'movimento') {
|
|
556
|
+
return movimentos.procurar({ termo: args.termo, categoria: args.categoria, limite: Number(args.limite) || 30 });
|
|
557
|
+
}
|
|
558
|
+
/* OS CENÁRIOS (cenarios.js, 12/10): os lugares prontos + os que a IA criou
|
|
559
|
+
e a pessoa deixou guardar. Salvar só com o sim dela (autorizado:true). */
|
|
560
|
+
if (acao === 'cenarios' || acao === 'cenários') {
|
|
561
|
+
return { ok: true, prontos: Object.keys(mundo.LUGARES), salvos: cenarios.listar(),
|
|
562
|
+
comoCriar: 'na cena do estilo personagem: lugar (um pronto como base), ceu [cor de cima, cor do horizonte], chao {tipo, cor, cor2}, parede, luz, acento, hora e elementos. Ficou bom? Pergunte se pode salvar e use acao:"salvar_cenario".' };
|
|
563
|
+
}
|
|
564
|
+
if (acao === 'salvar_cenario' || acao === 'salvar_cenário') {
|
|
565
|
+
return cenarios.salvar({ nome: args.nome, cenario: args.cenario, autorizado: args.autorizado, bases: Object.keys(mundo.LUGARES) });
|
|
566
|
+
}
|
|
530
567
|
if (acao === 'biblioteca' || acao === 'cardapio' || acao === 'cardápio') return colecao.cardapio();
|
|
531
568
|
if (acao === 'icones3d' || acao === 'icone3d' || acao === 'animacoes' || acao === 'animacao' || acao === 'animação' || acao === 'animações') {
|
|
532
569
|
const ehAnim = /^anima/.test(acao);
|
|
@@ -585,8 +622,9 @@ async function baixarEscolhido(lista, args, p) {
|
|
|
585
622
|
async function ferramentaNarrar(args) {
|
|
586
623
|
if (args.vozes) return rodarPython('narrar.py', ['vozes'], { timeout: 30000 });
|
|
587
624
|
|
|
588
|
-
const p =
|
|
625
|
+
const p = projetoOuRecente(args.id);
|
|
589
626
|
if (!p) return semProjeto(args.id);
|
|
627
|
+
if (args.id == null || String(args.id).trim() === '') args = { ...args, id: p.id };
|
|
590
628
|
const texto = String(args.texto || '').trim();
|
|
591
629
|
if (!texto) return { ok: false, error: 'não há texto para narrar.' };
|
|
592
630
|
|
|
@@ -616,8 +654,9 @@ async function ferramentaNarrar(args) {
|
|
|
616
654
|
|
|
617
655
|
async function ferramentaVideo(args, ctx = {}) {
|
|
618
656
|
const acao = String(args.acao || 'medir').toLowerCase();
|
|
619
|
-
const p =
|
|
657
|
+
const p = projetoOuRecente(args.id);
|
|
620
658
|
if (!p) return semProjeto(args.id);
|
|
659
|
+
if (args.id == null || String(args.id).trim() === '') args = { ...args, id: p.id };
|
|
621
660
|
|
|
622
661
|
const pronto = video.conferir();
|
|
623
662
|
if (!pronto.ok) return pronto;
|
|
@@ -1871,7 +1910,7 @@ async function prepararRoteiro(p, args) {
|
|
|
1871
1910
|
return r;
|
|
1872
1911
|
}
|
|
1873
1912
|
if (estilo === 'personagem' && modo === 'video') {
|
|
1874
|
-
const r = personagem.montarPersonagem({ roteiro: { ...roteiro, pedido: roteiro.pedido || pedidoTxt }, largura, altura, tempos, narracao, sons, trilha });
|
|
1913
|
+
const r = personagem.montarPersonagem({ roteiro: { ...roteiro, pedido: roteiro.pedido || pedidoTxt, voz: args.voz || roteiro.voz }, largura, altura, tempos, narracao, sons, trilha });
|
|
1875
1914
|
if (r.ok) {
|
|
1876
1915
|
/* O que não é peça do catálogo vira ícone 3D da biblioteca, em blocos
|
|
1877
1916
|
(kit/mundo.jsx): "uma pizza que fala", "um violão na praia". Sem
|
|
@@ -1884,6 +1923,7 @@ async function prepararRoteiro(p, args) {
|
|
|
1884
1923
|
if (b && b.ok) achados[nome] = b.src;
|
|
1885
1924
|
}
|
|
1886
1925
|
mundo.aplicarIcones(r.partitura, achados);
|
|
1926
|
+
mundo.adicionarObjetos(r.partitura, objetosDoRoteiro(roteiro.objetos, p.pasta));
|
|
1887
1927
|
const icones = Object.keys(achados);
|
|
1888
1928
|
projeto.anotar(p.id, 'plano', `estilo personagem: ${r.resumo.elenco.join(' e ')}${r.resumo.figurantes.length ? ` (+ ${r.resumo.figurantes.join(', ')})` : ''} em ${r.resumo.lugares.join(' → ')}, ${r.resumo.planos} planos, ${r.resumo.segundos}s${icones.length ? `; em blocos: ${icones.join(', ')}` : ''}`);
|
|
1889
1929
|
}
|
|
@@ -1962,8 +2002,9 @@ async function transcrever(p, alvo, ctx) {
|
|
|
1962
2002
|
// ── estudio_render ───────────────────────────────────────────────────────
|
|
1963
2003
|
|
|
1964
2004
|
async function ferramentaRender(args, ctx = {}) {
|
|
1965
|
-
const p =
|
|
2005
|
+
const p = projetoOuRecente(args.id);
|
|
1966
2006
|
if (!p) return semProjeto(args.id);
|
|
2007
|
+
if (args.id == null || String(args.id).trim() === '') args = { ...args, id: p.id };
|
|
1967
2008
|
const pl = projeto.lerPlano(args.id);
|
|
1968
2009
|
if (!pl) return { ok: false, error: 'não há plano neste projeto.' };
|
|
1969
2010
|
|
|
@@ -2209,7 +2250,7 @@ async function ferramentaSite(args) {
|
|
|
2209
2250
|
}
|
|
2210
2251
|
|
|
2211
2252
|
module.exports = {
|
|
2212
|
-
vazamentoNoComeco, variarIcones,
|
|
2253
|
+
vazamentoNoComeco, variarIcones, objetosDoRoteiro,
|
|
2213
2254
|
ferramentaProjeto, ferramentaPlano, ferramentaMidia, ferramentaNarrar,
|
|
2214
2255
|
ferramentaVideo, ferramentaRender, ferramentaSite,
|
|
2215
2256
|
projeto, plano, remotion, assets, video, identidade, slides, python, rodarPython, alinharPartes, prepararRoteiro, roteiroValido, juntarVozes,
|
|
@@ -706,13 +706,13 @@ function texturaCartaoUso(titulo, linhas, k, uso) {
|
|
|
706
706
|
return cv;
|
|
707
707
|
}
|
|
708
708
|
g.fillStyle = '#ff6a2b'; g.beginPath(); g.arc(46, 50, 9, 0, Math.PI * 2); g.fill();
|
|
709
|
-
g.fillStyle = '#f2f2f7'; g.font = `600 28px ${F}`; g.fillText(`
|
|
709
|
+
g.fillStyle = '#f2f2f7'; g.font = `600 28px ${F}`; g.fillText(`Primo · sessão ${k + 1}`, 66, 60); // a marca é a do PrimoCode, nunca a da referência (12/10)
|
|
710
710
|
g.fillStyle = '#ffffff'; g.font = `700 30px ${F}`; g.fillText(String(titulo).slice(0, 30), 34, 134);
|
|
711
711
|
g.fillStyle = 'rgba(225,225,235,0.75)'; g.font = `400 24px ${F}`;
|
|
712
712
|
(linhas || []).slice(0, 3).forEach((l, i) => g.fillText('· ' + String(l).slice(0, 34), 34, 178 + i * 36));
|
|
713
713
|
g.fillStyle = '#ff6a2b'; g.font = `500 22px ${F}`; g.fillText(`● trabalhando · ${30 + k * 7}:${String(10 + k * 13).slice(0, 2)}`, 34, 312);
|
|
714
714
|
g.fillStyle = 'rgba(255,255,255,0.06)'; g.beginPath(); g.roundRect(34, 336, 572, 40, 12); g.fill();
|
|
715
|
-
g.fillStyle = 'rgba(225,225,235,0.45)'; g.font = `400 20px ${F}`; g.fillText('
|
|
715
|
+
g.fillStyle = 'rgba(225,225,235,0.45)'; g.font = `400 20px ${F}`; g.fillText('Peça qualquer coisa…', 50, 363);
|
|
716
716
|
return cv;
|
|
717
717
|
}
|
|
718
718
|
function CartoesDeUso({ paineis = [], t }) {
|
|
@@ -289,18 +289,32 @@ function CabeloFacetado({ estilo, cor }) {
|
|
|
289
289
|
const u = hash(i * 3.7 + 1), v = hash(i * 9.1 + 4);
|
|
290
290
|
const az = u * Math.PI * 2;
|
|
291
291
|
const el = estilo === 'longo' ? -0.9 + v * 2.2 : 0.15 + v * 1.25;
|
|
292
|
-
|
|
293
|
-
|
|
292
|
+
/* O rosto olha para +z (fz = 0.335 na CabecaFacetada). Até a 10.7 o
|
|
293
|
+
filtro guardava cos(az) < 0.35 — z positivo, a FRENTE — e os cachos
|
|
294
|
+
caíam no olho e na bochecha (12/10). Agora a pedra fica atrás da linha
|
|
295
|
+
das orelhas, ou no alto da cabeça, acima da testa. */
|
|
296
|
+
const z = -Math.cos(az) * Math.cos(el) * 0.34;
|
|
297
|
+
const y = 0.04 + Math.sin(el) * 0.38;
|
|
298
|
+
if (z > 0.05 && y < 0.3) continue;
|
|
299
|
+
if (z > 0.18) continue;
|
|
294
300
|
const r = estilo === 'cacheado' ? 0.105 + hash(i) * 0.05 : 0.09 + hash(i) * 0.03;
|
|
295
|
-
out.push({ p: [Math.sin(az) * Math.cos(el) * 0.33,
|
|
301
|
+
out.push({ p: [Math.sin(az) * Math.cos(el) * 0.33, y, z - 0.02], r, k: i });
|
|
296
302
|
}
|
|
297
|
-
|
|
303
|
+
// O coque no alto e um pouco para trás: visível de frente (atrás, sumia e virava cabelo curto).
|
|
304
|
+
if (estilo === 'coque') out.push({ p: [0, 0.46, -0.12], r: 0.17, k: 99 });
|
|
298
305
|
return out;
|
|
299
306
|
}, [estilo]);
|
|
300
307
|
// A franja: a frente do cabelo, um pouco acima da sobrancelha.
|
|
301
308
|
return (
|
|
302
309
|
<group>
|
|
303
310
|
{estilo !== 'careca' && <Faceta p={[0, 0.2, -0.02]} s={[0.36, 0.26, 0.36]} c={cor} det={1} rough={0.9} />}
|
|
311
|
+
{/* O LONGO cai pelos lados até o ombro, por FORA da cabeça — as pedrinhas
|
|
312
|
+
ficavam dentro do crânio e, de frente, ela parecia de cabelo curto (12/10). */}
|
|
313
|
+
{estilo === 'longo' && <>
|
|
314
|
+
<Faceta p={[-0.33, -0.1, -0.04]} s={[0.11, 0.34, 0.24]} c={cor} det={1} rough={0.9} />
|
|
315
|
+
<Faceta p={[0.33, -0.1, -0.04]} s={[0.11, 0.34, 0.24]} c={cor} det={1} rough={0.9} />
|
|
316
|
+
<Faceta p={[0, -0.08, -0.26]} s={[0.34, 0.38, 0.13]} c={cor} det={1} rough={0.9} />
|
|
317
|
+
</>}
|
|
304
318
|
{pedras.map((x) => <Faceta key={x.k} p={x.p} s={[x.r, x.r * 0.9, x.r]} rot={[hash(x.k) * 3, hash(x.k + 1) * 3, 0]} c={escurecer(cor, 0.85 + hash(x.k + 2) * 0.3)} det={0} rough={0.95} />)}
|
|
305
319
|
</group>
|
|
306
320
|
);
|
|
@@ -599,9 +613,11 @@ function alturaDaCabeca(p) {
|
|
|
599
613
|
return 1.8;
|
|
600
614
|
}
|
|
601
615
|
/* Os figurantes ficam nas pontas do arco, um pouco atrás: estão na cena, mas não roubam o plano. */
|
|
602
|
-
function lugarDoFigurante(el, fig, j) {
|
|
616
|
+
function lugarDoFigurante(el, fig, j, vert = false) {
|
|
603
617
|
const n = Math.max(1, el.length);
|
|
604
|
-
|
|
618
|
+
/* No vertical o quadro é estreito: na ponta do arco (1,15) o gato pedido
|
|
619
|
+
ficava fora da tela o vídeo inteiro (12/10). Vem para perto, aos pés. */
|
|
620
|
+
const borda = vert ? (n === 1 ? 0.62 : n === 2 ? 1.1 : 1.6) : n === 1 ? 1.15 : n === 2 ? 1.75 : 2.5;
|
|
605
621
|
const lado = j % 2 ? 1 : -1;
|
|
606
622
|
const baixo = fig.tipo === 'animal' || fig.tipo === 'coisa';
|
|
607
623
|
return [lado * (borda + Math.floor(j / 2) * 0.9), 0, baixo ? 0.45 : -0.5];
|
|
@@ -836,7 +852,7 @@ export function VoxelCena({ el, t }) {
|
|
|
836
852
|
</group>
|
|
837
853
|
))}
|
|
838
854
|
{figurantes.map((p, j) => (
|
|
839
|
-
<group key={'f' + j} position={lugarDoFigurante(elenco, p, j)} rotation={[0, (j % 2 ? -1 : 1) * 0.5, 0]}>
|
|
855
|
+
<group key={'f' + j} position={lugarDoFigurante(elenco, p, j, W < H)} rotation={[0, (j % 2 ? -1 : 1) * 0.5, 0]}>
|
|
840
856
|
{desenhar(p, estado(10 + j, true))}
|
|
841
857
|
</group>
|
|
842
858
|
))}
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* movimentos.js — a biblioteca de MOVIMENTOS prontos: entradas, saídas,
|
|
3
|
+
* ênfases e loops, cada um com nome, que viram quadros-chave do `anima`.
|
|
4
|
+
*
|
|
5
|
+
* "preciso que você deixe os motions com + de 100 tipos de animações …
|
|
6
|
+
* além da IA poder criar os próprios e pesquisar" (Joel, 12/10)
|
|
7
|
+
*
|
|
8
|
+
* Até aqui todo movimento era escrito à mão pelo modelo, quadro a quadro
|
|
9
|
+
* (`anima: {opacidade: [{t, v}]}`). Modelo grátis escreve sempre os mesmos
|
|
10
|
+
* dois: esmaecer e subir. Com nome, ele PROCURA ("quique", "desfoque",
|
|
11
|
+
* "saída para a esquerda"), escolhe e escreve uma palavra:
|
|
12
|
+
*
|
|
13
|
+
* elemento.movimento = "quicar-de-baixo"
|
|
14
|
+
* elemento.movimento = {nome: "sair-girando", em: 2.4, duracao: 0.5}
|
|
15
|
+
* elemento.movimento = ["surgir-desfocado", {nome: "flutuar", em: 0.6}]
|
|
16
|
+
*
|
|
17
|
+
* O plano expande isso em `anima` antes de conferir (plano.js). O que a IA
|
|
18
|
+
* escreveu em `anima` vence o movimento pronto na mesma propriedade — e ela
|
|
19
|
+
* continua livre para inventar o seu, quadro a quadro.
|
|
20
|
+
*
|
|
21
|
+
* Os números: x/y/z são DESLOCAMENTOS em px (o render soma à posição), as
|
|
22
|
+
* rotações em graus, o desfoque em px, opacidade/escala/brilho/saturação em
|
|
23
|
+
* fração. Todo movimento de entrada termina no repouso (0 de deslocamento,
|
|
24
|
+
* escala 1, opacidade 1); toda saída começa nele.
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
'use strict';
|
|
28
|
+
|
|
29
|
+
// Cada quadro: [fração do tempo 0..1, valor, suavização até ele].
|
|
30
|
+
const DIRECOES = {
|
|
31
|
+
'de-baixo': { eixo: 'y', sinal: 1 }, 'de-cima': { eixo: 'y', sinal: -1 },
|
|
32
|
+
'da-esquerda': { eixo: 'x', sinal: -1 }, 'da-direita': { eixo: 'x', sinal: 1 },
|
|
33
|
+
};
|
|
34
|
+
const PARA = { 'de-baixo': 'para-baixo', 'de-cima': 'para-cima', 'da-esquerda': 'para-a-esquerda', 'da-direita': 'para-a-direita' };
|
|
35
|
+
|
|
36
|
+
/* As entradas com direção: cada estilo × as quatro direções. */
|
|
37
|
+
const ESTILOS_DIRECAO = {
|
|
38
|
+
deslizar: { d: 0.6, desc: 'desliza e aparece', f: (e, s) => ({ [e]: [[0, 80 * s], [1, 0, 'saida']], opacidade: [[0, 0], [0.6, 1, 'suave']] }) },
|
|
39
|
+
'deslizar-desfocado': { d: 0.7, desc: 'desliza saindo do desfoque', f: (e, s) => ({ [e]: [[0, 70 * s], [1, 0, 'saida']], opacidade: [[0, 0], [0.5, 1]], desfoque: [[0, 18], [1, 0, 'saida']] }) },
|
|
40
|
+
'chegar-elastico': { d: 0.9, desc: 'chega passando do ponto e volta, como mola', f: (e, s) => ({ [e]: [[0, 220 * s], [1, 0, 'elastico']], opacidade: [[0, 0], [0.25, 1]] }) },
|
|
41
|
+
quicar: { d: 0.9, desc: 'cai e quica', f: (e, s) => ({ [e]: [[0, 260 * s], [1, 0, 'salto']], opacidade: [[0, 0], [0.2, 1]] }) },
|
|
42
|
+
varrer: { d: 0.8, desc: 'atravessa rápido de longe e freia', f: (e, s) => ({ [e]: [[0, 900 * s], [1, 0, 'arrasto']], opacidade: [[0, 0], [0.3, 1]] }) },
|
|
43
|
+
'girar-entrando': { d: 0.8, desc: 'entra girando', f: (e, s) => ({ [e]: [[0, 160 * s], [1, 0, 'saida']], rotacao: [[0, -90 * s], [1, 0, 'saida']], opacidade: [[0, 0], [0.4, 1]] }) },
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
/* As entradas sem direção. */
|
|
47
|
+
const ENTRADAS = {
|
|
48
|
+
surgir: { d: 0.5, desc: 'só aparece (esmaecer)', f: () => ({ opacidade: [[0, 0], [1, 1, 'suave']] }) },
|
|
49
|
+
crescer: { d: 0.6, desc: 'cresce do nada', f: () => ({ escala: [[0, 0], [1, 1, 'saida']], opacidade: [[0, 0], [0.3, 1]] }) },
|
|
50
|
+
'crescer-elastico': { d: 0.9, desc: 'cresce como mola', f: () => ({ escala: [[0, 0], [1, 1, 'elastico']] }) },
|
|
51
|
+
'pular-pra-dentro': { d: 0.6, desc: 'cresce, passa um pouco e assenta', f: () => ({ escala: [[0, 0], [0.7, 1.15, 'saida'], [1, 1, 'suave']], opacidade: [[0, 0], [0.3, 1]] }) },
|
|
52
|
+
'encolher-pra-dentro': { d: 0.6, desc: 'vem grande e encolhe até o lugar', f: () => ({ escala: [[0, 1.8], [1, 1, 'saida']], opacidade: [[0, 0], [0.5, 1]] }) },
|
|
53
|
+
'surgir-desfocado': { d: 0.7, desc: 'sai do desfoque', f: () => ({ desfoque: [[0, 24], [1, 0, 'saida']], opacidade: [[0, 0], [0.6, 1]] }) },
|
|
54
|
+
'virar-de-lado': { d: 0.7, desc: 'vira em Y como carta', f: () => ({ rotacaoY: [[0, 90], [1, 0, 'saida']], opacidade: [[0, 0], [0.3, 1]] }) },
|
|
55
|
+
'virar-de-cima': { d: 0.7, desc: 'tomba em X, de cima para baixo', f: () => ({ rotacaoX: [[0, -90], [1, 0, 'saida']], opacidade: [[0, 0], [0.3, 1]] }) },
|
|
56
|
+
'girar-crescendo': { d: 0.8, desc: 'cresce dando meia volta', f: () => ({ rotacao: [[0, -180], [1, 0, 'saida']], escala: [[0, 0], [1, 1, 'saida']] }) },
|
|
57
|
+
'vir-do-fundo': { d: 0.8, desc: 'avança da profundidade (3D)', f: () => ({ z: [[0, -700], [1, 0, 'saida']], opacidade: [[0, 0], [0.4, 1]] }) },
|
|
58
|
+
'cair-do-alto': { d: 0.8, desc: 'cai do topo e assenta com um quique', f: () => ({ y: [[0, -500], [1, 0, 'salto']], rotacao: [[0, -12], [1, 0, 'saida']] }) },
|
|
59
|
+
'estourar-de-luz': { d: 0.6, desc: 'acende estourado e assenta', f: () => ({ brilho: [[0, 3], [1, 1, 'saida']], opacidade: [[0, 0], [0.3, 1]] }) },
|
|
60
|
+
'ganhar-cor': { d: 0.8, desc: 'sai do cinza para a cor', f: () => ({ saturacao: [[0, 0], [1, 1, 'suave']], opacidade: [[0, 0], [0.4, 1]] }) },
|
|
61
|
+
'abrir-letras': { d: 0.8, desc: 'as letras chegam juntas de longe (texto)', f: () => ({ espacamento: [[0, 40], [1, 0, 'saida']], opacidade: [[0, 0], [0.5, 1]] }) },
|
|
62
|
+
'esticar-de-lado': { d: 0.6, desc: 'estica na horizontal', f: () => ({ escalaX: [[0, 0], [1, 1, 'saida']] }) },
|
|
63
|
+
'esticar-de-pe': { d: 0.6, desc: 'estica na vertical', f: () => ({ escalaY: [[0, 0], [1, 1, 'saida']] }) },
|
|
64
|
+
'carimbo': { d: 0.35, desc: 'bate na tela como carimbo', f: () => ({ escala: [[0, 2.4], [1, 1, 'entrada']], opacidade: [[0, 0], [0.2, 1]] }) },
|
|
65
|
+
'aproximar-editorial': { d: 0.9, desc: 'entra devagar, como revista', f: () => ({ y: [[0, 24], [1, 0, 'editorial']], escala: [[0, 0.96], [1, 1, 'editorial']], opacidade: [[0, 0], [1, 1, 'editorial']] }) },
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
/* As ênfases: o elemento já está lá e chama atenção (começam e terminam no repouso). */
|
|
69
|
+
const ENFASES = {
|
|
70
|
+
pulsar: { d: 0.5, desc: 'cresce e volta uma vez', f: () => ({ escala: [[0, 1], [0.5, 1.12, 'suave'], [1, 1, 'suave']] }) },
|
|
71
|
+
tremer: { d: 0.5, desc: 'treme de lado (erro, negação)', f: () => ({ x: [[0, 0], [0.15, -14], [0.3, 12], [0.45, -10], [0.6, 8], [0.75, -4], [1, 0]] }) },
|
|
72
|
+
balancar: { d: 0.8, desc: 'balança como pêndulo', f: () => ({ rotacao: [[0, 0], [0.25, 12, 'suave'], [0.5, -8, 'suave'], [0.75, 4, 'suave'], [1, 0, 'suave']] }) },
|
|
73
|
+
piscar: { d: 0.6, desc: 'pisca duas vezes', f: () => ({ opacidade: [[0, 1], [0.25, 0.1], [0.5, 1], [0.75, 0.1], [1, 1]] }) },
|
|
74
|
+
'dar-uma-volta': { d: 0.8, desc: 'gira 360°', f: () => ({ rotacao: [[0, 0], [1, 360, 'suave']] }) },
|
|
75
|
+
'virar-moeda': { d: 0.8, desc: 'gira em Y como moeda', f: () => ({ rotacaoY: [[0, 0], [1, 360, 'suave']] }) },
|
|
76
|
+
'pular': { d: 0.6, desc: 'dá um pulinho', f: () => ({ y: [[0, 0], [0.4, -40, 'saida'], [1, 0, 'salto']] }) },
|
|
77
|
+
'tada': { d: 0.9, desc: 'cresce e chacoalha (conquista)', f: () => ({ escala: [[0, 1], [0.2, 0.9], [0.4, 1.12], [0.9, 1.12], [1, 1]], rotacao: [[0, 0], [0.3, -4], [0.45, 4], [0.6, -4], [0.75, 4], [1, 0]] }) },
|
|
78
|
+
'borracha': { d: 0.8, desc: 'amassa e estica como borracha', f: () => ({ escalaX: [[0, 1], [0.3, 1.25], [0.4, 0.75], [0.5, 1.15], [0.65, 0.95], [1, 1]], escalaY: [[0, 1], [0.3, 0.75], [0.4, 1.25], [0.5, 0.85], [0.65, 1.05], [1, 1]] }) },
|
|
79
|
+
'gelatina': { d: 0.9, desc: 'treme mole', f: () => ({ escalaX: [[0, 1], [0.2, 1.1], [0.4, 0.92], [0.6, 1.04], [0.8, 0.98], [1, 1]], escalaY: [[0, 1], [0.2, 0.92], [0.4, 1.08], [0.6, 0.97], [0.8, 1.01], [1, 1]] }) },
|
|
80
|
+
'acenar': { d: 0.8, desc: 'balança curto, como aceno', f: () => ({ rotacao: [[0, 0], [0.2, 14], [0.4, -8], [0.6, 14], [0.8, -4], [1, 0]] }) },
|
|
81
|
+
'acender': { d: 0.6, desc: 'brilha e apaga uma vez', f: () => ({ brilho: [[0, 1], [0.4, 1.8, 'suave'], [1, 1, 'suave']] }) },
|
|
82
|
+
'inclinar-3d': { d: 0.9, desc: 'inclina em 3D e volta', f: () => ({ rotacaoX: [[0, 0], [0.5, 18, 'suave'], [1, 0, 'suave']], rotacaoY: [[0, 0], [0.5, -14, 'suave'], [1, 0, 'suave']] }) },
|
|
83
|
+
'cabecear': { d: 0.6, desc: 'acena com a cabeça (sim)', f: () => ({ rotacaoX: [[0, 0], [0.3, 20], [0.6, -6], [1, 0]] }) },
|
|
84
|
+
'empurrao': { d: 0.4, desc: 'leva um soco e volta', f: () => ({ escala: [[0, 1], [0.25, 0.86, 'entrada'], [1, 1, 'elastico']] }) },
|
|
85
|
+
'sublinhar-luz': { d: 0.7, desc: 'fica vivo por um instante', f: () => ({ saturacao: [[0, 1], [0.4, 1.8, 'suave'], [1, 1, 'suave']], brilho: [[0, 1], [0.4, 1.3, 'suave'], [1, 1]] }) },
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
/* Os loops: repetem pela duração inteira (o padrão é a cena toda, 4 s). */
|
|
89
|
+
const LOOPS = {
|
|
90
|
+
flutuar: { d: 4, ciclo: 2.4, desc: 'sobe e desce devagar', f: () => ({ y: [[0, 0], [0.5, -16, 'suave'], [1, 0, 'suave']] }) },
|
|
91
|
+
respirar: { d: 4, ciclo: 2, desc: 'cresce e encolhe de leve', f: () => ({ escala: [[0, 1], [0.5, 1.05, 'suave'], [1, 1, 'suave']] }) },
|
|
92
|
+
'girar-sem-parar': { d: 4, ciclo: 4, desc: 'gira sem parar', f: () => ({ rotacao: [[0, 0], [1, 360, 'linear']] }) },
|
|
93
|
+
'oscilar': { d: 4, ciclo: 1.6, desc: 'balança de leve sem parar', f: () => ({ rotacao: [[0, -5], [0.5, 5, 'suave'], [1, -5, 'suave']] }) },
|
|
94
|
+
'batimento': { d: 4, ciclo: 1, desc: 'pulsa como coração', f: () => ({ escala: [[0, 1], [0.15, 1.12], [0.3, 1], [0.45, 1.08], [0.6, 1], [1, 1]] }) },
|
|
95
|
+
'piscar-devagar': { d: 4, ciclo: 1.6, desc: 'some e volta devagar', f: () => ({ opacidade: [[0, 1], [0.5, 0.35, 'suave'], [1, 1, 'suave']] }) },
|
|
96
|
+
'orbitar': { d: 4, ciclo: 3, desc: 'anda em círculo pequeno', f: () => ({ x: [[0, 20], [0.25, 0, 'suave'], [0.5, -20, 'suave'], [0.75, 0, 'suave'], [1, 20, 'suave']], y: [[0, 0], [0.25, 20, 'suave'], [0.5, 0, 'suave'], [0.75, -20, 'suave'], [1, 0, 'suave']] }) },
|
|
97
|
+
'deriva': { d: 4, ciclo: 4, desc: 'anda de lado bem devagar', f: () => ({ x: [[0, -12], [0.5, 12, 'suave'], [1, -12, 'suave']] }) },
|
|
98
|
+
'girar-3d': { d: 4, ciclo: 5, desc: 'gira em Y sem parar, como vitrine', f: () => ({ rotacaoY: [[0, 0], [1, 360, 'linear']] }) },
|
|
99
|
+
'brilho-vivo': { d: 4, ciclo: 2, desc: 'o brilho respira', f: () => ({ brilho: [[0, 1], [0.5, 1.25, 'suave'], [1, 1, 'suave']] }) },
|
|
100
|
+
'balanco-3d': { d: 4, ciclo: 3, desc: 'inclina em 3D de um lado para o outro', f: () => ({ rotacaoY: [[0, -12], [0.5, 12, 'suave'], [1, -12, 'suave']] }) },
|
|
101
|
+
'tremor-leve': { d: 4, ciclo: 0.3, desc: 'vibra o tempo todo (tensão, alerta)', f: () => ({ x: [[0, 0], [0.25, -2], [0.5, 2], [0.75, -1], [1, 0]] }) },
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
/* A SAÍDA é a entrada ao contrário: o mesmo desenho, tempo invertido. */
|
|
105
|
+
function inverter(gerado) {
|
|
106
|
+
const out = {};
|
|
107
|
+
for (const [prop, qs] of Object.entries(gerado)) {
|
|
108
|
+
const ordem = [...qs].reverse();
|
|
109
|
+
// A suavização de um quadro vale ATÉ ele; invertida, passa ao vizinho.
|
|
110
|
+
out[prop] = ordem.map(([f, v], i) => [1 - f, v, i > 0 ? inverterE(ordem[i - 1][2]) : undefined]);
|
|
111
|
+
}
|
|
112
|
+
return out;
|
|
113
|
+
}
|
|
114
|
+
const inverterE = (e) => ({ saida: 'entrada', entrada: 'saida', arrasto: 'acelera', acelera: 'arrasto', elastico: 'entrada', salto: 'entrada' }[e] || e);
|
|
115
|
+
const nomeDaSaida = (n) => n.replace(/^surgir/, 'sumir').replace(/^crescer/, 'encolher-sumindo').replace(/^chegar/, 'partir')
|
|
116
|
+
.replace(/^vir-do-fundo/, 'ir-pro-fundo').replace(/^cair-do-alto/, 'cair-pra-fora').replace(/^ganhar-cor/, 'perder-cor')
|
|
117
|
+
.replace(/^abrir-letras/, 'espalhar-letras').replace(/^estourar-de-luz/, 'apagar-estourando');
|
|
118
|
+
|
|
119
|
+
function montarCatalogo() {
|
|
120
|
+
const cat = {};
|
|
121
|
+
const pore = (nome, categoria, def, gerar) => { cat[nome] = { nome, categoria, descricao: def.desc, duracao: def.d, ciclo: def.ciclo, gerar }; };
|
|
122
|
+
for (const [estilo, def] of Object.entries(ESTILOS_DIRECAO)) {
|
|
123
|
+
for (const [dir, { eixo, sinal }] of Object.entries(DIRECOES)) {
|
|
124
|
+
const gerar = () => def.f(eixo, sinal);
|
|
125
|
+
pore(`${estilo}-${dir}`, 'entrada', { ...def, desc: `${def.desc}, vindo ${dir.replace('-', ' ')}` }, gerar);
|
|
126
|
+
pore(`${estilo}-${PARA[dir]}`.replace(/^deslizar-desfocado/, 'sair-desfocando').replace(/^deslizar/, 'sair-deslizando')
|
|
127
|
+
.replace(/^chegar-elastico/, 'sair-elastico').replace(/^quicar/, 'sair-quicando').replace(/^varrer/, 'sair-varrendo')
|
|
128
|
+
.replace(/^girar-entrando/, 'sair-girando'), 'saida', { ...def, desc: `sai: ${def.desc.split(',')[0]}, ${PARA[dir].replace(/-/g, ' ')}` },
|
|
129
|
+
() => inverter(def.f(eixo, sinal)));
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
for (const [nome, def] of Object.entries(ENTRADAS)) {
|
|
133
|
+
pore(nome, 'entrada', def, def.f);
|
|
134
|
+
pore(nomeDaSaida(nome) === nome ? `${nome}-saindo` : nomeDaSaida(nome), 'saida', { ...def, desc: `sai: ${def.desc}` }, () => inverter(def.f()));
|
|
135
|
+
}
|
|
136
|
+
for (const [nome, def] of Object.entries(ENFASES)) pore(nome, 'enfase', def, def.f);
|
|
137
|
+
for (const [nome, def] of Object.entries(LOOPS)) pore(nome, 'loop', def, def.f);
|
|
138
|
+
return cat;
|
|
139
|
+
}
|
|
140
|
+
const CATALOGO = montarCatalogo();
|
|
141
|
+
|
|
142
|
+
const r3 = (n) => Math.round(n * 1000) / 1000;
|
|
143
|
+
const semAcento = (s) => String(s || '').normalize('NFD').replace(/[̀-ͯ]/g, '').toLowerCase();
|
|
144
|
+
|
|
145
|
+
/** Os quadros-chave de um movimento, a partir de `em` segundos. */
|
|
146
|
+
const achar = (nome) => CATALOGO[semAcento(nome).trim().replace(/\s+/g, '-')] || null;
|
|
147
|
+
|
|
148
|
+
function quadros(nome, { em = 0, duracao } = {}) {
|
|
149
|
+
const m = achar(nome);
|
|
150
|
+
if (!m) return null;
|
|
151
|
+
const d = Number(duracao) > 0 ? Number(duracao) : m.duracao;
|
|
152
|
+
const gerado = m.gerar();
|
|
153
|
+
const anima = {};
|
|
154
|
+
if (m.categoria === 'loop') {
|
|
155
|
+
const ciclo = Math.min(m.ciclo || d, d);
|
|
156
|
+
const voltas = Math.max(1, Math.round(d / ciclo));
|
|
157
|
+
for (const [prop, qs] of Object.entries(gerado)) {
|
|
158
|
+
const lista = [];
|
|
159
|
+
for (let k = 0; k < voltas; k++) {
|
|
160
|
+
for (const [f, v, e] of qs) {
|
|
161
|
+
if (k > 0 && f === 0) continue; // o fim de uma volta é o começo da próxima
|
|
162
|
+
// o giro sem parar soma: a 2ª volta vai de 360 a 720
|
|
163
|
+
const soma = /rotacao/.test(prop) && qs[qs.length - 1][1] === 360 ? 360 * k : 0;
|
|
164
|
+
lista.push({ t: r3(em + (k + f) * ciclo), v: v + soma, ...(e ? { e } : {}) });
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
anima[prop] = lista;
|
|
168
|
+
}
|
|
169
|
+
return anima;
|
|
170
|
+
}
|
|
171
|
+
for (const [prop, qs] of Object.entries(gerado)) anima[prop] = qs.map(([f, v, e]) => ({ t: r3(em + f * d), v, ...(e ? { e } : {}) }));
|
|
172
|
+
return anima;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/** Procura por palavra (no nome ou na descrição) e por categoria. Sem termo, devolve um pouco de cada. */
|
|
176
|
+
function procurar({ termo = '', categoria = '', limite = 30 } = {}) {
|
|
177
|
+
const palavras = semAcento(termo).split(/[^a-z0-9]+/).filter(Boolean);
|
|
178
|
+
const cat = semAcento(categoria).replace('ê', 'e');
|
|
179
|
+
const lista = Object.values(CATALOGO).filter((m) => (!cat || m.categoria === cat.replace(/s$/, ''))
|
|
180
|
+
&& palavras.every((p) => semAcento(m.nome + ' ' + m.descricao).includes(p)));
|
|
181
|
+
return {
|
|
182
|
+
ok: true, total: Object.keys(CATALOGO).length, achados: lista.length,
|
|
183
|
+
movimentos: lista.slice(0, limite).map((m) => ({ nome: m.nome, categoria: m.categoria, faz: m.descricao, duracao: m.duracao })),
|
|
184
|
+
comoUsar: 'no elemento do plano: movimento:"<nome>" ou {nome, em (s), duracao (s)} ou uma lista deles. O que você escrever em anima vence na mesma propriedade; pode inventar o seu em anima, quadro a quadro.',
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Troca `movimento` por `anima` em todos os elementos do plano. Muda o plano
|
|
190
|
+
* no lugar e devolve os nomes que não existem (o plano.js vira problema).
|
|
191
|
+
*/
|
|
192
|
+
function expandir(plano) {
|
|
193
|
+
const desconhecidos = [];
|
|
194
|
+
for (const [i, cena] of ((plano && plano.cenas) || []).entries()) {
|
|
195
|
+
for (const [j, el] of ((cena && cena.elementos) || []).entries()) {
|
|
196
|
+
if (!el || el.movimento == null) continue;
|
|
197
|
+
const pedidos = (Array.isArray(el.movimento) ? el.movimento : [el.movimento]).map((m) => (typeof m === 'string' ? { nome: m } : m || {}));
|
|
198
|
+
const juntado = {};
|
|
199
|
+
for (const p of pedidos) {
|
|
200
|
+
const a = quadros(p.nome, { em: Number(p.em) || 0, duracao: p.duracao ?? ((achar(p.nome) || {}).categoria === 'loop' ? cena.duracao : undefined) });
|
|
201
|
+
if (!a) { desconhecidos.push({ onde: `cenas[${i}].elementos[${j}].movimento`, nome: String(p.nome) }); continue; }
|
|
202
|
+
// Dois movimentos na mesma propriedade: os quadros se somam na linha do tempo.
|
|
203
|
+
for (const [prop, qs] of Object.entries(a)) juntado[prop] = [...(juntado[prop] || []), ...qs].sort((x, y) => x.t - y.t);
|
|
204
|
+
}
|
|
205
|
+
el.anima = { ...juntado, ...(el.anima || {}) };
|
|
206
|
+
delete el.movimento;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
return desconhecidos;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
module.exports = { CATALOGO, quadros, procurar, expandir, inverter };
|
package/lib/estudio/mundo.js
CHANGED
|
@@ -237,7 +237,11 @@ function posicionar(nomes, { semente = 1, agua = false, dentro = false, largura
|
|
|
237
237
|
* pedido da pessoa: as coisas que ele cita entram na cena), semente, nElenco}
|
|
238
238
|
*/
|
|
239
239
|
function mundoDe(pedido, { texto = '', pedidoTexto = '', semente = 1, nElenco = 1 } = {}) {
|
|
240
|
-
|
|
240
|
+
let obj = pedido && typeof pedido === 'object' ? pedido : { lugar: String(pedido || '') };
|
|
241
|
+
/* Um cenário que a IA criou e a pessoa deixou salvar (cenarios.js): o
|
|
242
|
+
nome dele no lugar traz tudo de volta; o que a cena disser por cima vence. */
|
|
243
|
+
const salvo = require('./cenarios.js').ler(obj.cenario || obj.lugar);
|
|
244
|
+
if (salvo) obj = { ...salvo, ...obj, lugar: salvo.base, hora: obj.hora || salvo.hora, elementos: obj.elementos || salvo.elementos };
|
|
241
245
|
const descr = [obj.lugar, obj.descricao, obj.hora].filter(Boolean).join(' ');
|
|
242
246
|
let { lugar, hora } = lerLugar(descr);
|
|
243
247
|
const nomeDireto = sem(obj.lugar).replace(/\s+/g, '_');
|
|
@@ -302,4 +306,23 @@ function aplicarIcones(partitura, achados) {
|
|
|
302
306
|
return partitura;
|
|
303
307
|
}
|
|
304
308
|
|
|
305
|
-
|
|
309
|
+
/**
|
|
310
|
+
* As peças que a IA TROUXE para a cena: a logo, o botão ou o produto recortado
|
|
311
|
+
* do site (estudio_midia acao:elemento), um PNG sem fundo que vira blocos como
|
|
312
|
+
* os ícones 3D. "Imagens e parte de sites devem entrar no motion como ícone
|
|
313
|
+
* 3D" (Joel, 12/10). Ficam na frente, no centro, um pouco maiores que o resto
|
|
314
|
+
* do lugar: é o assunto. No máximo três, para não virar vitrine.
|
|
315
|
+
*/
|
|
316
|
+
function adicionarObjetos(partitura, objetos) {
|
|
317
|
+
const lista = (Array.isArray(objetos) ? objetos : []).filter((o) => o && o.src).slice(0, 3);
|
|
318
|
+
if (!lista.length) return partitura;
|
|
319
|
+
for (const m of partitura.mundos || []) {
|
|
320
|
+
lista.forEach((o, i) => {
|
|
321
|
+
const x = (i - (lista.length - 1) / 2) * 2.4;
|
|
322
|
+
m.props.push({ icone: String(o.nome || 'objeto').slice(0, 40), src: o.src, x, z: -1.6, raio: 1, giro: 0, escala: 1.35, trazido: true });
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
return partitura;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
module.exports = { mundoDe, lerLugar, lerCoisas, lerElenco, iconesPendentes, aplicarIcones, adicionarObjetos, LUGARES, HORAS, PECAS, ESPECIES, sem };
|
|
@@ -29,6 +29,7 @@
|
|
|
29
29
|
|
|
30
30
|
const { volumesDaTrilha } = require('./ritmo.js');
|
|
31
31
|
const mundo = require('./mundo.js');
|
|
32
|
+
const falantes = require('./falantes.js');
|
|
32
33
|
const colecao = require('./colecao.js');
|
|
33
34
|
|
|
34
35
|
const FPS = 30;
|
|
@@ -110,6 +111,23 @@ function tipoDe(nome, pedido = {}) {
|
|
|
110
111
|
return 'pessoa';
|
|
111
112
|
}
|
|
112
113
|
|
|
114
|
+
/* QUEM APRESENTA TEM O GÊNERO DA VOZ (12/10: "Narradora: a Júlia" saiu com
|
|
115
|
+
voz de mulher e boneco de homem). O pedido manda primeiro ("narradora",
|
|
116
|
+
"apresentadora", o nome dela), depois a voz escolhida. */
|
|
117
|
+
const VOZES_F = /\b(kore|aoede|leda|zephyr|thalita|francisca|brenda|elza|giovanna|leila|leticia|manuela|yara|camila|vitoria)/i;
|
|
118
|
+
const VOZES_M = /\b(charon|puck|orus|fenrir|antonio|donato|fabio|humberto|julio|nicolau|valerio)/i;
|
|
119
|
+
function generoDoApresentador(roteiro = {}) {
|
|
120
|
+
const pedido = mundo.sem(roteiro.pedido || '');
|
|
121
|
+
if (/\b(narradora|apresentadora|locutora)\b/.test(pedido)) return 'f';
|
|
122
|
+
if (/\b(narrador|apresentador|locutor)\b/.test(pedido)) return 'm';
|
|
123
|
+
const voz = String(roteiro.voz || '');
|
|
124
|
+
if (VOZES_F.test(voz)) return 'f';
|
|
125
|
+
if (VOZES_M.test(voz)) return 'm';
|
|
126
|
+
return null;
|
|
127
|
+
}
|
|
128
|
+
const CABELOS_F = ['longo']; // o coque e o cacheado, de frente, liam como homem (12/10); só quando pedidos
|
|
129
|
+
const CABELOS_M = ['curto', 'topete', 'careca', 'cacheado'];
|
|
130
|
+
|
|
113
131
|
function aparencia(nome, pedido = {}) {
|
|
114
132
|
const k = (s) => hashDe(nome + ':' + s);
|
|
115
133
|
const tipo = tipoDe(nome, pedido);
|
|
@@ -128,12 +146,14 @@ function aparencia(nome, pedido = {}) {
|
|
|
128
146
|
return {
|
|
129
147
|
nome, rotulo, tipo,
|
|
130
148
|
pele: pedido.pele || pegar(PELES, k('pele')),
|
|
131
|
-
cabelo: CABELOS.includes(pedido.cabelo) ? pedido.cabelo
|
|
149
|
+
cabelo: CABELOS.includes(pedido.cabelo) ? pedido.cabelo
|
|
150
|
+
: pegar(pedido.genero === 'f' ? CABELOS_F : pedido.genero === 'm' ? CABELOS_M : CABELOS, k('cabelo')),
|
|
132
151
|
corCabelo: pedido.corCabelo || pegar(CORES_CABELO, k('corCabelo')),
|
|
133
152
|
roupa: pedido.roupa || pegar(ROUPAS, k('roupa')),
|
|
134
153
|
oculos: pedido.oculos != null ? !!pedido.oculos : k('oculos') < 0.3,
|
|
135
|
-
barba: !!pedido.barba, // só quando pedida: sorteada, ficava estranha em quem tem cabelo longo
|
|
136
|
-
bigode: !!pedido.bigode,
|
|
154
|
+
barba: !!pedido.barba && pedido.genero !== 'f', // só quando pedida: sorteada, ficava estranha em quem tem cabelo longo
|
|
155
|
+
bigode: !!pedido.bigode && pedido.genero !== 'f',
|
|
156
|
+
...(pedido.genero ? { genero: pedido.genero } : {}),
|
|
137
157
|
// A cabeça facetada (low-poly) é o padrão; 'cubo' é o rosto de pixel da 10.5.
|
|
138
158
|
...(pedido.cabeca === 'cubo' ? { cabeca: 'cubo' } : {}),
|
|
139
159
|
};
|
|
@@ -316,7 +336,13 @@ function partituraDe({ roteiro = {}, largura = 1920, altura = 1080, tempos = []
|
|
|
316
336
|
return bicho ? { ...dado, tipo: 'animal', especie: bicho[1] } : dado;
|
|
317
337
|
};
|
|
318
338
|
// Até QUATRO falam no set ("três amigos no parque"); do quinto em diante dividem o lugar.
|
|
319
|
-
const
|
|
339
|
+
const comGenero = (n) => {
|
|
340
|
+
const d = pedidoDe(n);
|
|
341
|
+
if (d.genero) return d;
|
|
342
|
+
const g = n === 'apresentador' ? generoDoApresentador(roteiro) : falantes.generoDe(n, d);
|
|
343
|
+
return g === 'f' || g === 'm' ? { ...d, genero: g } : d;
|
|
344
|
+
};
|
|
345
|
+
const elenco = nomes.slice(0, 4).map((n, i) => ({ ...aparencia(n, comGenero(n)), lado: nomes.length > 1 ? (i < nomes.length / 2 ? -1 : 1) : 0, ordem: i }));
|
|
320
346
|
const quemDe = (pt) => {
|
|
321
347
|
const f = typeof pt.falante === 'string' && pt.falante.trim() ? pt.falante.trim().toLowerCase() : 'apresentador';
|
|
322
348
|
return nomes.indexOf(f) % Math.max(1, elenco.length);
|
|
@@ -453,4 +479,4 @@ function montarPersonagem({ roteiro, largura = 1920, altura = 1080, tempos = [],
|
|
|
453
479
|
};
|
|
454
480
|
}
|
|
455
481
|
|
|
456
|
-
module.exports = { calarBichos, montarPersonagem, partituraDe, planosDe, paineisDe, aparencia, tipoDe, acaoDe, destaqueDe, emocaoDe, CENARIOS, CENARIOS_VOXEL, EH_ROBO, FPS };
|
|
482
|
+
module.exports = { generoDoApresentador, calarBichos, montarPersonagem, partituraDe, planosDe, paineisDe, aparencia, tipoDe, acaoDe, destaqueDe, emocaoDe, CENARIOS, CENARIOS_VOXEL, EH_ROBO, FPS };
|
package/lib/estudio/plano.js
CHANGED
|
@@ -49,6 +49,7 @@ const fs = require('fs');
|
|
|
49
49
|
const path = require('path');
|
|
50
50
|
const morfar = require('./morfar.js');
|
|
51
51
|
const kit = require('./kit.js');
|
|
52
|
+
const movimentos = require('./movimentos.js');
|
|
52
53
|
|
|
53
54
|
// "kit": uma peça pronta do kit de motion (kit.js), pelo nome — ver kit/*.jsx.
|
|
54
55
|
const TIPOS = new Set(['texto', 'forma', 'imagem', 'video', 'icone', 'legenda', 'componente', 'grupo', 'kit']);
|
|
@@ -283,6 +284,11 @@ function conferir(plano, { pasta = null } = {}) {
|
|
|
283
284
|
if (!plano || typeof plano !== 'object') {
|
|
284
285
|
return { ok: false, problemas: [erro('plano', 'o plano é um objeto JSON')], duracao: 0 };
|
|
285
286
|
}
|
|
287
|
+
/* `movimento: "quicar-de-baixo"` vira quadros-chave (movimentos.js) antes
|
|
288
|
+
de tudo: o que segue confere o `anima` como se a IA o tivesse escrito. */
|
|
289
|
+
for (const d of movimentos.expandir(plano)) {
|
|
290
|
+
problemas.push(erro(d.onde, `o movimento "${d.nome}" não existe — procure com estudio_midia acao:"movimentos" termo:"…"`));
|
|
291
|
+
}
|
|
286
292
|
|
|
287
293
|
const f = plano.formato || {};
|
|
288
294
|
const largura = Number(f.largura) || 1920;
|
package/lib/nuvem.js
CHANGED
|
@@ -24,13 +24,12 @@
|
|
|
24
24
|
* A alternativa era o servidor intermediar cada leitura e escrita, com uma
|
|
25
25
|
* autorização própria. Seria um SEGUNDO modelo de permissão ao lado das
|
|
26
26
|
* `firestore.rules`, e os dois divergiriam na primeira mudança delas. Aqui o
|
|
27
|
-
* CLI
|
|
28
|
-
*
|
|
29
|
-
* exatamente como valem para o app e para o celular.
|
|
27
|
+
* CLI usa o token de acesso da própria conta (lib/conta.js) na API REST: as
|
|
28
|
+
* regras do banco valem para o terminal exatamente como valem para o site.
|
|
30
29
|
*
|
|
31
30
|
* ── ZERO DEPENDÊNCIA ─────────────────────────────────────────────────────
|
|
32
31
|
* Nada de `firebase-admin` nem do SDK web. O que se usa é o `crypto` e o
|
|
33
|
-
* `https` do Node, contra a API REST do Firestore
|
|
32
|
+
* `https` do Node, contra a API REST do Firestore.
|
|
34
33
|
*/
|
|
35
34
|
|
|
36
35
|
'use strict';
|
|
@@ -41,13 +40,12 @@ const http = require('http');
|
|
|
41
40
|
|
|
42
41
|
const conta = require('./conta.js');
|
|
43
42
|
|
|
44
|
-
/*
|
|
43
|
+
/* O endereço sai daqui em vez de estarem escritos no meio do código.
|
|
45
44
|
Não é para trocar de nuvem: é para o teste conseguir pôr um Firestore de
|
|
46
45
|
mentira no lugar e CONFERIR O QUE SOBE. Sem isso, a única forma de saber se
|
|
47
46
|
um título vai em claro seria olhar o console do Firebase depois — que é
|
|
48
47
|
exatamente tarde demais. */
|
|
49
48
|
const FIRESTORE = process.env.PRIMOCODE_FIRESTORE || 'https://firestore.googleapis.com';
|
|
50
|
-
const IDENTIDADE = process.env.PRIMOCODE_IDENTITY || 'https://identitytoolkit.googleapis.com';
|
|
51
49
|
|
|
52
50
|
const VERSAO_CIFRA = 2; // o mesmo `v` do frontend/crypto.js
|
|
53
51
|
const ITERACOES = 310000;
|
|
@@ -132,26 +130,17 @@ function httpsJson(url, metodo, corpo, cabecalhos = {}, timeout = 20000) {
|
|
|
132
130
|
|
|
133
131
|
let _sessao = null; // { uid, idToken, expira, projeto }
|
|
134
132
|
|
|
135
|
-
/* Abre (ou renova) a sessão. O
|
|
136
|
-
|
|
137
|
-
|
|
133
|
+
/* Abre (ou renova) a sessão. O conta.sessao() já devolve o token de acesso
|
|
134
|
+
válido por mais 5 minutos no mínimo (renova sozinho): aqui só se guarda,
|
|
135
|
+
para não ler o plano a cada mensagem. Guardado por 4 minutos. */
|
|
138
136
|
async function abrir() {
|
|
139
|
-
if (_sessao && Date.now() < _sessao.expira
|
|
137
|
+
if (_sessao && Date.now() < _sessao.expira) return _sessao;
|
|
140
138
|
const s = await conta.sessao();
|
|
141
|
-
if (!s.ok) return null;
|
|
142
|
-
if (!s.apiKey || !s.projeto) {
|
|
143
|
-
// O servidor não está configurado para isto (falta FIREBASE_API_KEY /
|
|
144
|
-
// FIREBASE_PROJECT_ID no ambiente dele). Dizer é melhor que tentar e
|
|
145
|
-
// falhar com "requisição inválida" mais adiante.
|
|
146
|
-
return null;
|
|
147
|
-
}
|
|
148
|
-
const r = await httpsJson(
|
|
149
|
-
`${IDENTIDADE}/v1/accounts:signInWithCustomToken?key=${s.apiKey}`,
|
|
150
|
-
'POST', { token: s.customToken, returnSecureToken: true });
|
|
139
|
+
if (!s.ok || !s.idToken) return null;
|
|
151
140
|
_sessao = {
|
|
152
141
|
uid: s.uid, projeto: s.projeto, plano: s.plano, pro: s.pro,
|
|
153
|
-
idToken:
|
|
154
|
-
expira: Date.now() +
|
|
142
|
+
idToken: s.idToken,
|
|
143
|
+
expira: Date.now() + 240000,
|
|
155
144
|
};
|
|
156
145
|
return _sessao;
|
|
157
146
|
}
|