primocode 10.2.0 → 10.3.0-beta.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/lib/act.js +5 -0
- package/lib/browser.js +1 -1
- package/lib/catalogo.js +16 -7
- package/lib/estudio/assinatura.js +108 -0
- package/lib/estudio/assinaturas/fluido.json +15 -0
- package/lib/estudio/assinaturas/gerativo.json +15 -0
- package/lib/estudio/diretor.js +70 -4
- package/lib/estudio/efeitos.js +95 -0
- package/lib/estudio/falantes.js +97 -0
- package/lib/estudio/fluido.js +28 -3
- package/lib/estudio/gerativo.js +309 -85
- package/lib/estudio/hibrido.js +1 -1
- package/lib/estudio/index.js +130 -15
- package/lib/estudio/kit/gerativo.jsx +90 -21
- package/lib/estudio/kit/marca.jsx +58 -1
- package/lib/estudio/kit/tres.jsx +4 -1
- package/lib/estudio/kit.js +2 -0
- package/lib/estudio/plano.js +5 -1
- package/lib/estudio/print-site.js +152 -0
- package/lib/estudio/referencias.js +12 -1
- package/lib/estudio/remotion.js +72 -3
- package/lib/estudio/variedade.js +13 -5
- package/package.json +2 -2
package/lib/estudio/fluido.js
CHANGED
|
@@ -51,6 +51,31 @@ const PALETAS = {
|
|
|
51
51
|
};
|
|
52
52
|
const ESCURAS = ['noite', 'breu', 'preto', 'cobalto'], CLARAS = ['lavanda', 'creme', 'laranja', 'branco'];
|
|
53
53
|
|
|
54
|
+
/* AS FONTES (09/10): "vício em fontes" (Joel) — todo fluido saía em Inter +
|
|
55
|
+
Instrument Serif + JetBrains Mono. Agora é um TRIO do cardápio: o grotesco
|
|
56
|
+
leve das palavras (o kit usa 300 a 600), a serifa itálica do acento e o mono
|
|
57
|
+
do terminal. A memória da variedade ('fonte-fluido') não repete o trio dos
|
|
58
|
+
últimos vídeos; o modelo pode pedir um pelo nome (roteiro.fontesFluido) e o
|
|
59
|
+
pedido vale, menos quando acabou de sair. `css` é o pedido ao Google Fonts
|
|
60
|
+
(conferido: todos respondem 200). Sem escolha salva, o trio aprovado. */
|
|
61
|
+
const TRIOS = {
|
|
62
|
+
aprovado: { texto: 'Inter', serifa: 'Instrument Serif', mono: 'JetBrains Mono', css: ['wght@300..700', 'ital@0;1', 'wght@400;500'] },
|
|
63
|
+
editorial: { texto: 'Hanken Grotesk', serifa: 'Fraunces', mono: 'Martian Mono', css: ['wght@300..700', 'ital,wght@0,400;1,400', 'wght@400;500'] },
|
|
64
|
+
revista: { texto: 'Figtree', serifa: 'Playfair Display', mono: 'IBM Plex Mono', css: ['wght@300..700', 'ital,wght@0,400;1,400', 'wght@400;500'] },
|
|
65
|
+
carater: { texto: 'Bricolage Grotesque', serifa: 'Bodoni Moda', mono: 'Space Mono', css: ['wght@300..700', 'ital,wght@0,400;1,400', 'wght@400'] },
|
|
66
|
+
tecnico: { texto: 'Mona Sans', serifa: 'Instrument Serif', mono: 'Fragment Mono', css: ['wght@300..700', 'ital@0;1', 'ital@0;1'] },
|
|
67
|
+
classico: { texto: 'Albert Sans', serifa: 'DM Serif Display', mono: 'DM Mono', css: ['wght@300..700', 'ital@0;1', 'wght@400;500'] },
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
/** O trio de um projeto novo, pelos pontos da variedade. Roda uma vez por projeto (index.js guarda). */
|
|
71
|
+
function escolherFontes(pedido = null, { aleatorio = Math.random } = {}) {
|
|
72
|
+
const variedade = require('./variedade.js');
|
|
73
|
+
const nome = TRIOS[String(pedido || '').toLowerCase()] ? String(pedido).toLowerCase() : null;
|
|
74
|
+
// O pedido do modelo vale mais (1), os outros servem (0,5): penaliza o recente, não proíbe.
|
|
75
|
+
return variedade.sortear(Object.keys(TRIOS), 'fonte-fluido', { aleatorio, adequacao: (k) => (!nome || k === nome ? 1 : 0.5) });
|
|
76
|
+
}
|
|
77
|
+
const trioDe = (roteiro) => TRIOS[roteiro.fontesFluido] || TRIOS.aprovado;
|
|
78
|
+
|
|
54
79
|
const hash = (s) => { let h = 2166136261; for (const c of String(s)) h = Math.imul(h ^ c.charCodeAt(0), 16777619); return h >>> 0; };
|
|
55
80
|
|
|
56
81
|
/** A paleta: a pedida pelo nome; senão o tema (claro/escuro); senão a semente. */
|
|
@@ -295,7 +320,7 @@ function partituraDe({ roteiro, largura = 1920, altura = 1080, tempos = [], imag
|
|
|
295
320
|
return {
|
|
296
321
|
versao: 2, semente, fps: FPS, atraso, sincronia, largura, altura, duracao: ult.ate, paleta,
|
|
297
322
|
marcador: roteiro.marcador || ['brilho', 'numero', 'traco', 'ponto', 'nenhum'][hash('marcador' + semente) % 5],
|
|
298
|
-
fontes: { texto
|
|
323
|
+
fontes: (({ texto, serifa, mono }) => ({ texto, serifa, mono }))(trioDe(roteiro)),
|
|
299
324
|
logo: marca.logo || null,
|
|
300
325
|
batidas,
|
|
301
326
|
};
|
|
@@ -334,7 +359,7 @@ function montarFluido({ roteiro, largura = 1920, altura = 1080, tempos = [], nar
|
|
|
334
359
|
const plano = {
|
|
335
360
|
formato: { largura, altura, fps: FPS },
|
|
336
361
|
fundoGeral: P.fundo[0],
|
|
337
|
-
fontesVariaveis: {
|
|
362
|
+
fontesVariaveis: (({ texto, serifa, mono, css }) => ({ [texto]: css[0], [serifa]: css[1], [mono]: css[2] }))(trioDe(roteiro)),
|
|
338
363
|
cenas: [{
|
|
339
364
|
duracao: DUR,
|
|
340
365
|
fundo: P.fundo[0],
|
|
@@ -349,4 +374,4 @@ function montarFluido({ roteiro, largura = 1920, altura = 1080, tempos = [], nar
|
|
|
349
374
|
return { ok: true, plano, partitura, resumo: { cenas: 1, batidas: partitura.batidas.length, imagens: partitura.batidas.filter((b) => b.imagem).map((b) => b.imagem.jeito), segundos: DUR, fps: FPS, paleta: P.nome, atraso: partitura.atraso, sincronia: partitura.sincronia } };
|
|
350
375
|
}
|
|
351
376
|
|
|
352
|
-
module.exports = { montarFluido, partituraDe, caixaNormal, PALETAS, POR_PALAVRA, MINIMO };
|
|
377
|
+
module.exports = { montarFluido, partituraDe, caixaNormal, escolherFontes, PALETAS, TRIOS, POR_PALAVRA, MINIMO };
|
package/lib/estudio/gerativo.js
CHANGED
|
@@ -209,12 +209,132 @@ const ICONE_DE = {
|
|
|
209
209
|
|
|
210
210
|
/** O humor do roteiro: as palavras dele contra as de cada humor (empate: a semente). */
|
|
211
211
|
function humorDe(roteiro, s) {
|
|
212
|
-
const
|
|
213
|
-
+ ' ' + ((roteiro.marca && roteiro.marca.nome) || '') + ' ' + (roteiro.pedido || '')));
|
|
212
|
+
const n = acertosDeHumor(roteiro);
|
|
214
213
|
const nota = {};
|
|
215
|
-
for (const
|
|
214
|
+
for (const h of Object.keys(HUMORES)) nota[h] = n[h] + s.prox() * 0.5;
|
|
216
215
|
return Object.entries(nota).sort((a, b) => b[1] - a[1])[0][0];
|
|
217
216
|
}
|
|
217
|
+
/** Quantas palavras de cada humor o roteiro diz. */
|
|
218
|
+
function acertosDeHumor(roteiro) {
|
|
219
|
+
const txt = new Set(tokens((roteiro.partes || []).map((p) => [p.titulo, p.texto, p.fala, p.sub, ...(p.itens || [])].filter(Boolean).join(' ')).join(' ')
|
|
220
|
+
+ ' ' + ((roteiro.marca && roteiro.marca.nome) || '') + ' ' + (roteiro.pedido || '')));
|
|
221
|
+
return Object.fromEntries(Object.entries(HUMORES).map(([h, d]) => [h, d.palavras.split(' ').filter((w) => txt.has(w)).length]));
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/* AS ESCOLHAS DO PROJETO (09/10): "vício em cores, músicas, fontes… ele
|
|
225
|
+
escolhe sempre as mesmas coisas" (Joel). A paleta, as fontes e o gênero
|
|
226
|
+
saíam só da semente — sem lembrar do vídeo anterior — e o humor caía em
|
|
227
|
+
"tecnologia" com uma palavra só ("dados", "rápido"), porque é a lista mais
|
|
228
|
+
longa. Agora as quatro escolhas passam pela memória da variedade.js:
|
|
229
|
+
- o humor: adequação (acertos + 1) / (máximo + 1). Com o assunto claro
|
|
230
|
+
(6 palavras de IA), os outros valem ~0,15 e a tecnologia fica; com uma
|
|
231
|
+
palavra só, os outros valem 0,5 e a memória gira. Penaliza, não proíbe;
|
|
232
|
+
- paleta, fontes e gênero: as do humor valem 1, as dos outros humores
|
|
233
|
+
valem menos (ainda curadas, nunca as de cara de IA) — o que é do assunto
|
|
234
|
+
vem primeiro, e quando já saiu nos últimos vídeos a vizinha ganha.
|
|
235
|
+
Roda UMA vez por projeto (index.js guarda em p.escolhasGerativo): mandar
|
|
236
|
+
o roteiro de novo não sorteia outro vídeo. A partitura segue pura — ela só
|
|
237
|
+
lê roteiro.escolhasGerativo, e a marca continua mandando por cima. */
|
|
238
|
+
function escolherVisual(roteiro = {}, { aleatorio = Math.random } = {}) {
|
|
239
|
+
const variedade = require('./variedade.js');
|
|
240
|
+
const marca = roteiro.marca || {};
|
|
241
|
+
const n = acertosDeHumor(roteiro);
|
|
242
|
+
const max = Math.max(...Object.values(n));
|
|
243
|
+
const humor = variedade.sortear(Object.keys(HUMORES), 'humor-gerativo', { aleatorio, adequacao: (h) => (n[h] + 1) / (max + 1) });
|
|
244
|
+
const de = (campo, outros) => { const m = {}; for (const [h, d] of Object.entries(HUMORES)) for (const x of d[campo]) if (!BANIDAS.includes(x) && (m[x] == null || h === humor)) m[x] = h === humor ? 1 : outros; return m; };
|
|
245
|
+
const escolher = (m, tipo, prefixo) => variedade.sortear(Object.keys(m), tipo, { aleatorio, prefixo, adequacao: m });
|
|
246
|
+
const out = { humor };
|
|
247
|
+
// A paleta é o sistema de 5 campos (é o que aparece). Com o fundo da marca, nem sorteia.
|
|
248
|
+
if (!ehCor((marca.cores || {}).fundo)) {
|
|
249
|
+
const m = {};
|
|
250
|
+
for (const [h, lista] of Object.entries(CAMPOS)) for (const k of lista) m[k.join(' ')] = h === humor ? 1 : 0.6;
|
|
251
|
+
out.paleta = escolher(m, 'paleta-gerativo', '').split(' ');
|
|
252
|
+
}
|
|
253
|
+
const fontesMarca = marca.fontes || {};
|
|
254
|
+
if (!fontesMarca.titulo) out.display = escolher(de('displays', 0.5), 'fonte-gerativo', 'display:');
|
|
255
|
+
if (!fontesMarca.texto) out.texto = escolher(de('textos', 0.5), 'fonte-gerativo', 'texto:');
|
|
256
|
+
out.mono = escolher(de('monos', 0.5), 'fonte-gerativo', 'mono:');
|
|
257
|
+
out.genero = escolher(de('generos', 0.35), 'genero-gerativo', '');
|
|
258
|
+
const um = (lista, tipo, prefixo) => variedade.sortear(lista, tipo, { aleatorio, prefixo });
|
|
259
|
+
const o3d = OBJETO3D_DO_HUMOR[humor] != null ? OBJETO3D_DO_HUMOR[humor] : 0.7;
|
|
260
|
+
// Um pouco de acaso na adequação: sem ele, a memória vira rodízio fixo (esfera+explosão, polígono+toro, de novo).
|
|
261
|
+
const fitH = (h) => (HEROIS_OBJETO.includes(h) ? o3d : 1 - o3d / 2) * (0.75 + 0.25 * aleatorio());
|
|
262
|
+
/* A FORMA ABSTRATA (09/10, "partículas girando numa conversa de padaria?"):
|
|
263
|
+
só no assunto abstrato ou de tecnologia, e com força pelo quanto o
|
|
264
|
+
roteiro É do assunto (uma palavra só não basta). UMA forma por vídeo —
|
|
265
|
+
é identidade, não sorteio por batida — ou 'nenhum', que a memória também gira. */
|
|
266
|
+
const forca = (ABSTRATO_DO_HUMOR[humor] || 0) * Math.min(1, n[humor] / 3);
|
|
267
|
+
const h1 = forca > 0 ? variedade.sortear([...HEROIS_OBJETO, ...HEROIS_CAMPO, 'nenhum'], 'heroi-gerativo', { aleatorio, adequacao: (h) => (h === 'nenhum' ? 1 - forca : forca * fitH(h)) }) : 'nenhum';
|
|
268
|
+
out.herois = { favoritos: h1 === 'nenhum' ? [] : [h1] };
|
|
269
|
+
// 'produto' e 'marca' quem decide é o conteúdo (o 3D pedido, a marca), não o gosto do projeto.
|
|
270
|
+
const favC = [];
|
|
271
|
+
for (let k = 0; k < 3; k++) favC.push(variedade.sortear(COMPOSICOES.filter((c) => c !== 'produto' && c !== 'marca' && !favC.includes(c)), 'composicao-gerativo', { aleatorio }));
|
|
272
|
+
out.composicoes = { favoritas: favC };
|
|
273
|
+
/* A MOLDURA (09/10): o quadro fixo do reel de referência — cantos, o assunto
|
|
274
|
+
no alto, a seção, o tempo embaixo — é identidade, mas em todo vídeo vira
|
|
275
|
+
vício. Sai do cardápio com memória: 'sem' pesa um pouco mais. */
|
|
276
|
+
out.moldura = variedade.sortear(MOLDURAS, 'moldura-gerativo', { aleatorio, adequacao: { sem: 1, cantos: 0.95, completa: 0.95 } });
|
|
277
|
+
// O bokeh combina com luz de tela (tecnologia, energia, natureza); numa padaria, quase nunca.
|
|
278
|
+
const bokeh = ['tecnologia', 'energia', 'natureza'].includes(humor) ? 0.6 : 0.2;
|
|
279
|
+
out.ambiente = variedade.sortear(AMBIENTES, 'ambiente-gerativo', { aleatorio, adequacao: { limpo: 1, luz: 0.85, bokeh } });
|
|
280
|
+
out.acento = variedade.sortear(ACENTOS_JEITO, 'acento-gerativo', { aleatorio, adequacao: { traco: 1, viagem: 0.7 } });
|
|
281
|
+
out.icones = { arranjo: um(ARRANJOS_ICONE, 'arranjo-icone', 'arranjo:'), ritmo: um(RITMOS_ICONE, 'arranjo-icone', 'ritmo:'), material: um(MATERIAIS_ICONE, 'material-icone', '') };
|
|
282
|
+
return out;
|
|
283
|
+
}
|
|
284
|
+
/* OS ÍCONES (09/10, "vício na forma que os ícones são distribuídos"): o
|
|
285
|
+
ícone ia sempre à direita (deitado) ou acima (em pé), nas primeiras batidas
|
|
286
|
+
que tinham palavra-chave, num de 3 materiais. Agora o projeto escolhe:
|
|
287
|
+
- o ARRANJO: 'lado' (o de antes), 'outro-lado' (esquerda / abaixo),
|
|
288
|
+
'alterna' (um de cada lado, batida a batida) ou 'canto' (menor, no canto);
|
|
289
|
+
- o RITMO: 'seguidos' (o de antes), 'alternados' (uma batida sem ícone entre
|
|
290
|
+
dois) ou 'esparsos' (duas);
|
|
291
|
+
- o MATERIAL do kit/tres.jsx.
|
|
292
|
+
O kit tenta o lugar pedido e, sem espaço, o próximo da lista (nunca por cima do texto). */
|
|
293
|
+
/* O HERÓI E AS COMPOSIÇÕES entre vídeos (09/10, "vício no 3D… efeitos mais 3D
|
|
294
|
+
dependendo do vídeo"): dentro do vídeo eles já não se repetiam, mas o vídeo
|
|
295
|
+
seguinte recomeçava igual. Cada projeto escolhe 2 heróis e 3 composições
|
|
296
|
+
FAVORITOS pela memória (pesam mais no sorteio; os outros continuam valendo).
|
|
297
|
+
O assunto decide quanto 3D: tecnologia e energia puxam os objetos (esfera,
|
|
298
|
+
toro, polígono, explosão); editorial e luxo, os campos. */
|
|
299
|
+
const HEROIS_OBJETO = ['explosao', 'poligono', 'esfera', 'toro'], HEROIS_CAMPO = ['truchet', 'aneis', 'grade', 'rastro'];
|
|
300
|
+
const OBJETO3D_DO_HUMOR = { tecnologia: 1, energia: 1, natureza: 0.8, luxo: 0.6, editorial: 0.4 };
|
|
301
|
+
/* Quanto o assunto aceita forma abstrata (o resto: a tipografia e o ícone do conteúdo). */
|
|
302
|
+
const ABSTRATO_DO_HUMOR = { tecnologia: 1, energia: 0.7, natureza: 0.5, luxo: 0, editorial: 0 };
|
|
303
|
+
const ARRANJOS_ICONE = ['lado', 'outro-lado', 'alterna', 'canto'];
|
|
304
|
+
const MOLDURAS = ['sem', 'cantos', 'completa'];
|
|
305
|
+
/* A BOLINHA (09/10): "todo vídeo tem aquela bolinha" (Joel). O ambiente:
|
|
306
|
+
'limpo' (só grão e vinheta), 'luz' (o brilho e o feixe que varre) ou
|
|
307
|
+
'bokeh' (as manchas de luz desfocadas na frente). O acento: 'traco' (cada
|
|
308
|
+
traço se desenha e some) ou 'viagem' (o ponto que nasce no meio, viaja
|
|
309
|
+
entre as batidas e se recolhe — o de antes). */
|
|
310
|
+
const AMBIENTES = ['limpo', 'luz', 'bokeh'];
|
|
311
|
+
const ACENTOS_JEITO = ['traco', 'viagem'];
|
|
312
|
+
const RITMOS_ICONE = ['seguidos', 'alternados', 'esparsos'];
|
|
313
|
+
const MATERIAIS_ICONE = ['argila', 'verniz', 'metal', 'perola', 'acetinado'];
|
|
314
|
+
|
|
315
|
+
/** Só o que existe nos cardápios: um p.escolhasGerativo velho ou editado à mão não quebra o vídeo. */
|
|
316
|
+
function escolhasValidas(e) {
|
|
317
|
+
if (!e || typeof e !== 'object') return { herois: [], composicoes: [] };
|
|
318
|
+
const todas = (campo) => Object.values(HUMORES).flatMap((d) => d[campo]);
|
|
319
|
+
return {
|
|
320
|
+
humor: HUMORES[e.humor] ? e.humor : null,
|
|
321
|
+
moldura: MOLDURAS.includes(e.moldura) ? e.moldura : null,
|
|
322
|
+
ambiente: AMBIENTES.includes(e.ambiente) ? e.ambiente : null,
|
|
323
|
+
acento: ACENTOS_JEITO.includes(e.acento) ? e.acento : null,
|
|
324
|
+
paleta: Array.isArray(e.paleta) && e.paleta.length === 5 && e.paleta.every(ehCor) ? e.paleta : null,
|
|
325
|
+
display: VARIAVEIS[e.display] && todas('displays').includes(e.display) ? e.display : null,
|
|
326
|
+
texto: todas('textos').includes(e.texto) ? e.texto : null,
|
|
327
|
+
mono: todas('monos').includes(e.mono) ? e.mono : null,
|
|
328
|
+
genero: todas('generos').includes(e.genero) ? e.genero : null,
|
|
329
|
+
herois: (e.herois && Array.isArray(e.herois.favoritos) ? e.herois.favoritos : []).filter((h) => HEROIS_OBJETO.includes(h) || HEROIS_CAMPO.includes(h)),
|
|
330
|
+
composicoes: (e.composicoes && Array.isArray(e.composicoes.favoritas) ? e.composicoes.favoritas : []).filter((c) => COMPOSICOES.includes(c)),
|
|
331
|
+
icones: e.icones && ARRANJOS_ICONE.includes(e.icones.arranjo) ? {
|
|
332
|
+
arranjo: e.icones.arranjo,
|
|
333
|
+
ritmo: RITMOS_ICONE.includes(e.icones.ritmo) ? e.icones.ritmo : 'seguidos',
|
|
334
|
+
material: MATERIAIS_ICONE.includes(e.icones.material) ? e.icones.material : null,
|
|
335
|
+
} : null,
|
|
336
|
+
};
|
|
337
|
+
}
|
|
218
338
|
/* As que têm largura (wdth): é nelas que a palavra estica. */
|
|
219
339
|
const CINETICAS = ['Roboto Flex', 'Anybody', 'Archivo', 'Mona Sans'];
|
|
220
340
|
|
|
@@ -452,10 +572,13 @@ const luz = (h) => { const n = parseInt(String(h).slice(1), 16); return ((n >> 1
|
|
|
452
572
|
const contraste = (a, b) => { const la = luz(a) + 0.05, lb = luz(b) + 0.05; return la > lb ? la / lb : lb / la; };
|
|
453
573
|
const PALETAS = HUMORES.tecnologia.paletas;
|
|
454
574
|
|
|
455
|
-
function paletaDe(marca, s, humor = null, roteiro = {}) {
|
|
575
|
+
function paletaDe(marca, s, humor = null, roteiro = {}, fixa = null) {
|
|
456
576
|
const c = marca.cores || {};
|
|
457
577
|
const op = humor ? HUMORES[humor].paletas : PALETAS;
|
|
458
|
-
|
|
578
|
+
// O sorteio acontece sempre: a sequência da semente não muda com ou sem escolha salva.
|
|
579
|
+
const sorteada = op[Math.floor(s.prox() * op.length) % op.length];
|
|
580
|
+
// A escolha do projeto (escolherVisual) é um sistema de 5 campos: 0 = tinta escura, 1 = assinatura, 2 = papel.
|
|
581
|
+
const [f0, t0, a0] = fixa ? [fixa[0], fixa[2], fixa[1]] : sorteada;
|
|
459
582
|
let base = { fundo: f0, tinta: t0, acento: a0 };
|
|
460
583
|
// A cor que o assunto diz manda no acento (se não sumir no fundo).
|
|
461
584
|
const ditas = tokens((roteiro.partes || []).map((p) => [p.titulo, p.destaque].join(' ')).join(' ')).filter((w) => CORES_DITAS[w]);
|
|
@@ -478,9 +601,10 @@ const CAMPOS = {
|
|
|
478
601
|
natureza: [['#0F2A2A', '#F2A541', '#F2EDE2', '#2546D9', '#E4573D'], ['#13233F', '#2546D9', '#F2EDE2', '#F2A541', '#C8553D']],
|
|
479
602
|
luxo: [['#120D0B', '#C9A24A', '#F0E6D6', '#5B1A1F', '#2F4F4F']],
|
|
480
603
|
};
|
|
481
|
-
function camposDe(marca, humor, s, dita = null) {
|
|
604
|
+
function camposDe(marca, humor, s, dita = null, fixa = null) {
|
|
482
605
|
const c = marca.cores || {};
|
|
483
|
-
|
|
606
|
+
const sorteado = s.um(CAMPOS[humor] || CAMPOS.tecnologia);
|
|
607
|
+
let k = [...(fixa || sorteado)];
|
|
484
608
|
// A cor que o assunto diz vira a assinatura (se não for a de outro papel).
|
|
485
609
|
if (dita && !ehCor(c.destaque)) { const j = k.indexOf(dita); if (j > 0) k[j] = k[1]; k[1] = dita; }
|
|
486
610
|
// A marca: o fundo dela, o destaque dela e a tinta dela tomam os lugares de mesmo papel.
|
|
@@ -492,15 +616,56 @@ function camposDe(marca, humor, s, dita = null) {
|
|
|
492
616
|
if (ehCor(c.destaque) && k[1] !== c.destaque) k[1] = c.destaque;
|
|
493
617
|
return k;
|
|
494
618
|
}
|
|
495
|
-
|
|
496
|
-
|
|
619
|
+
/* A IDENTIDADE TRAVADA (09/10): "as cores do nada estão preto e do nada estão
|
|
620
|
+
branco, cores nada a ver uma diferente da outra" (Joel, sobre o Pix na
|
|
621
|
+
padaria). O sistema de 5 campos virava 4 fundos trocando quase toda batida.
|
|
622
|
+
Agora o vídeo usa UMA identidade: 2 chãos (o escuro e o claro do sistema) e
|
|
623
|
+
1 acento (a assinatura), e mais nada. Os campos 3 e 4 ficam de fora. */
|
|
624
|
+
function identidadeDe(campos, marca = {}) {
|
|
625
|
+
const c = marca.cores || {};
|
|
626
|
+
let chaos = [campos[0], campos[2]];
|
|
627
|
+
// O fundo da marca é sempre um chão (mesmo o de meio-tom, que o camposDe punha na assinatura).
|
|
628
|
+
if (ehCor(c.fundo) && !chaos.includes(c.fundo)) chaos[luz(c.fundo) < 0.5 ? 0 : 1] = c.fundo;
|
|
629
|
+
chaos = chaos.sort((a, b) => luz(a) - luz(b));
|
|
630
|
+
return { chaos, acento: campos[1] };
|
|
631
|
+
}
|
|
632
|
+
/** O acento no chão: se some nele, escurece (chão claro) ou clareia (chão escuro) no MESMO matiz. */
|
|
633
|
+
function acentoNoChao(acento, fundo) {
|
|
634
|
+
if (contraste(acento, fundo) >= 2.6) return acento;
|
|
635
|
+
const alvo = luz(fundo) > 0.5 ? '#000000' : '#ffffff';
|
|
636
|
+
for (let k = 0.1; k <= 0.9; k += 0.05) { const x = misturarCor(acento, alvo, k); if (contraste(x, fundo) >= 2.6) return x; }
|
|
637
|
+
return misturarCor(acento, alvo, 0.9);
|
|
638
|
+
}
|
|
639
|
+
/** As cores de uma batida pelo chão dela: texto no outro chão, acento o da identidade. */
|
|
640
|
+
function coresDoCampo(campos, i, s, id = null) {
|
|
641
|
+
const I = id || identidadeDe(campos);
|
|
497
642
|
const fundo = campos[i];
|
|
498
|
-
const
|
|
499
|
-
const
|
|
500
|
-
const outras = campos.filter((c, j) => j !== i && c !== texto && contraste(c, fundo) >= 2.2);
|
|
501
|
-
const acento = outras.length ? (outras.includes(campos[1]) && i !== 1 ? campos[1] : s.um(outras)) : texto;
|
|
643
|
+
const texto = [...I.chaos].sort((a, b) => contraste(b, fundo) - contraste(a, fundo))[0];
|
|
644
|
+
const acento = acentoNoChao(I.acento, fundo);
|
|
502
645
|
return { fundo, texto, fraco: misturarCor(texto, fundo, 0.45), acento, contorno: misturarCor(texto, fundo, 0.6) };
|
|
503
646
|
}
|
|
647
|
+
/* AS SEÇÕES: o fundo troca só na virada de seção. Quem diz a seção é o
|
|
648
|
+
roteiro (parte.secao, o nome dela); sem isso, as partes se dividem em 1 a 4
|
|
649
|
+
blocos seguidos, uns 3 por bloco. Devolve o índice de cada parte e os nomes. */
|
|
650
|
+
function secoesDe(partes) {
|
|
651
|
+
const n = partes.length;
|
|
652
|
+
if (partes.some((p) => p && typeof p.secao === 'string' && p.secao.trim())) {
|
|
653
|
+
const nomes = [], idx = [];
|
|
654
|
+
let atual = null;
|
|
655
|
+
partes.forEach((p, i) => {
|
|
656
|
+
const nome = (p.secao && String(p.secao).trim()) || atual || '';
|
|
657
|
+
if (i === 0 || nome !== atual) nomes.push(nome);
|
|
658
|
+
atual = nome;
|
|
659
|
+
idx.push(nomes.length - 1);
|
|
660
|
+
});
|
|
661
|
+
return { idx, nomes };
|
|
662
|
+
}
|
|
663
|
+
const k = n <= 3 ? 1 : n <= 6 ? 2 : n <= 10 ? 3 : 4;
|
|
664
|
+
const idx = partes.map((_, i) => Math.floor((i * k) / n));
|
|
665
|
+
// Sem nome dado, a seção leva o título da primeira parte dela.
|
|
666
|
+
const nomes = Array.from({ length: k }, (_, j) => { const p = partes[idx.indexOf(j)] || {}; return String(p.titulo || p.texto || p.sim || p.rotulo || p.valor || '').trim(); });
|
|
667
|
+
return { idx, nomes };
|
|
668
|
+
}
|
|
504
669
|
|
|
505
670
|
/** As cores de uma batida pelo fundo dela: base (a marca) ou acento (o fundo vira a cor). */
|
|
506
671
|
function coresDaBatida(P, fundo) {
|
|
@@ -523,7 +688,9 @@ const NOTAS = [220, 246.94, 261.63, 293.66, 329.63, 349.23, 392, 440];
|
|
|
523
688
|
function sonsDoVideo(roteiro = {}) {
|
|
524
689
|
const semente = hash('som:' + JSON.stringify((roteiro.partes || []).map((p) => [p.titulo, p.fala])) + ((roteiro.marca && roteiro.marca.nome) || ''));
|
|
525
690
|
const s = sorteador(semente);
|
|
526
|
-
const
|
|
691
|
+
const humorSom = humorDe(roteiro, s);
|
|
692
|
+
const E = escolhasValidas(roteiro.escolhasGerativo);
|
|
693
|
+
const timbre = HUMORES[E.humor || humorSom] ? HUMORES[E.humor || humorSom].som : 'analogico';
|
|
527
694
|
const raiz = s.um(NOTAS);
|
|
528
695
|
const escala = [1, 1.189, 1.335, 1.498, 1.782, 2]; // pentatônica menor sobre a raiz
|
|
529
696
|
const nota = () => Math.round(raiz * s.um(escala) * (s.chance(0.3) ? 2 : 1));
|
|
@@ -587,8 +754,11 @@ function sonsDoVideo(roteiro = {}) {
|
|
|
587
754
|
function musicaDe(roteiro = {}) {
|
|
588
755
|
const semente = hash('musica:' + JSON.stringify((roteiro.partes || []).map((p) => [p.titulo, p.fala])));
|
|
589
756
|
const s = sorteador(semente);
|
|
590
|
-
const
|
|
591
|
-
|
|
757
|
+
const E = escolhasValidas(roteiro.escolhasGerativo);
|
|
758
|
+
const sorteado = humorDe(roteiro, s);
|
|
759
|
+
const h = E.humor || sorteado;
|
|
760
|
+
const genero = s.um(HUMORES[h].generos);
|
|
761
|
+
return { humor: h, genero: E.genero || genero, semente };
|
|
592
762
|
}
|
|
593
763
|
/* ── O SOM DESENHADO (30/09, terceira volta): "os efeitos repetem e são os
|
|
594
764
|
genéricos que toda IA usa". Então:
|
|
@@ -697,10 +867,10 @@ const LEGADO = { passagem: ['whoosh', 'sopro'], golpe: ['impacto', 'toque'], nas
|
|
|
697
867
|
* @param {Array} op.tempos [{de, ate, palavras:[{texto,de,ate}]}] por parte
|
|
698
868
|
* @returns {{ok, plano, partitura, resumo}}
|
|
699
869
|
*/
|
|
700
|
-
function montarGerativo({ roteiro, largura = 1920, altura = 1080, tempos = [], narracao = null, sons = {}, trilha = null }) {
|
|
870
|
+
function montarGerativo({ roteiro, largura = 1920, altura = 1080, tempos = [], narracao = null, sons = {}, trilha = null, icones = {} }) {
|
|
701
871
|
const partes = ((roteiro && roteiro.partes) || []).filter((p) => p && typeof p === 'object');
|
|
702
872
|
if (!partes.length) return { ok: false, error: 'o roteiro veio sem partes.' };
|
|
703
|
-
const partitura = partituraDe({ roteiro: { ...roteiro, partes }, largura, altura, tempos });
|
|
873
|
+
const partitura = partituraDe({ roteiro: { ...roteiro, partes }, largura, altura, tempos, icones });
|
|
704
874
|
const DUR = partitura.duracao;
|
|
705
875
|
|
|
706
876
|
// ── o som: efeitos nas transformações, a música abaixa sob a voz ─────
|
|
@@ -741,16 +911,21 @@ function montarGerativo({ roteiro, largura = 1920, altura = 1080, tempos = [], n
|
|
|
741
911
|
}
|
|
742
912
|
|
|
743
913
|
/** A partitura, sem áudio nem plano — é o que os testes olham. */
|
|
744
|
-
function partituraDe({ roteiro, largura = 1920, altura = 1080, tempos = [] }) {
|
|
914
|
+
function partituraDe({ roteiro, largura = 1920, altura = 1080, tempos = [], icones = {} }) {
|
|
745
915
|
const partes = roteiro.partes || [];
|
|
746
916
|
const marca = roteiro.marca || {};
|
|
747
917
|
const W = largura, H = altura, deitado = W >= H;
|
|
748
918
|
const semente = roteiro.semente != null ? hash('semente:' + roteiro.semente)
|
|
749
919
|
: hash(JSON.stringify([partes.map((p) => [p.tipo, p.titulo, p.texto, p.fala, p.valor, p.itens, p.nao, p.sim, p.destaque]), marca.nome || '']));
|
|
750
920
|
const s = sorteador(semente);
|
|
751
|
-
|
|
921
|
+
/* As escolhas do projeto (escolherVisual, com memória entre vídeos). Sem
|
|
922
|
+
elas — projeto antigo, teste — vale a semente, como antes. Os sorteios
|
|
923
|
+
da semente acontecem igual nos dois casos: o resto do vídeo não muda. */
|
|
924
|
+
const E = escolhasValidas(roteiro.escolhasGerativo);
|
|
925
|
+
const humorSorteado = humorDe(roteiro, s);
|
|
926
|
+
const humor = E.humor || humorSorteado;
|
|
752
927
|
const H0 = HUMORES[humor];
|
|
753
|
-
const P = paletaDe(marca, s, humor, roteiro);
|
|
928
|
+
const P = paletaDe(marca, s, humor, roteiro, E.paleta);
|
|
754
929
|
|
|
755
930
|
/* ── as fontes: NADA de cara de IA. A da marca vale se tiver caráter (e
|
|
756
931
|
eixos para animar); a genérica da marca vira texto, e o título ganha
|
|
@@ -758,11 +933,16 @@ function partituraDe({ roteiro, largura = 1920, altura = 1080, tempos = [] }) {
|
|
|
758
933
|
const pedida = (marca.fontes && marca.fontes.titulo) || '';
|
|
759
934
|
const pedidaTexto = (marca.fontes && marca.fontes.texto) || '';
|
|
760
935
|
const boa = (f) => f && VARIAVEIS[f] && !BANIDAS.includes(f) && VARIAVEIS[f].eixos.wght;
|
|
761
|
-
const
|
|
936
|
+
const ou = (salva, sorteada) => salva || sorteada;
|
|
937
|
+
const display = boa(pedida) ? pedida : ou(E.display, s.um(H0.displays));
|
|
762
938
|
const cinetica = eixoDe(display, 'wdth') && eixoDe(display, 'wdth')[0] <= 75 ? display : s.um(H0.cineticas);
|
|
763
|
-
const texto = pedidaTexto ? pedidaTexto : (pedida && BANIDAS.includes(pedida) ? pedida : s.um(H0.textos));
|
|
939
|
+
const texto = pedidaTexto ? pedidaTexto : (pedida && BANIDAS.includes(pedida) ? pedida : ou(E.texto, s.um(H0.textos)));
|
|
764
940
|
const serifa = VARIAVEIS[display].serifa ? display : s.um(H0.serifas);
|
|
765
|
-
|
|
941
|
+
/* DUAS FONTES (09/10, a identidade): o título e o texto. A cinética, a
|
|
942
|
+
serifa da citação e o rótulo eram mais três faces — o vídeo saía com 4
|
|
943
|
+
ou 5 famílias. Os sorteios continuam (a semente não muda o resto). */
|
|
944
|
+
void cinetica; void serifa; void ou(E.mono, s.um(H0.monos));
|
|
945
|
+
const fontes = { display, cinetica: display, texto, mono: texto, serifa: display };
|
|
766
946
|
|
|
767
947
|
// ── o quadro: margens e respiro ─────────────────────────────────────
|
|
768
948
|
// A margem conta com a câmera: ela empurra até ~8% e desliza, e nada pode sair do quadro.
|
|
@@ -821,6 +1001,7 @@ function partituraDe({ roteiro, largura = 1920, altura = 1080, tempos = [] }) {
|
|
|
821
1001
|
if (recentes.slice(-3).includes(comp)) q *= 0.35;
|
|
822
1002
|
q *= Math.pow(0.72, usadas[comp] || 0);
|
|
823
1003
|
if (comp === 'parede' && (usadas.parede || 0) >= 1) q *= 0.25;
|
|
1004
|
+
if (E.composicoes.includes(comp)) q *= 2.2; // a favorita do projeto (escolherVisual)
|
|
824
1005
|
w[nome] = q;
|
|
825
1006
|
}
|
|
826
1007
|
return s.pesado(w) || s.pesado({ pilha: anterior === 'pilha' ? 0 : 1, gigante: anterior === 'gigante' ? 0 : 1, palavras: anterior === 'palavras' ? 0 : 1 });
|
|
@@ -879,72 +1060,92 @@ function partituraDe({ roteiro, largura = 1920, altura = 1080, tempos = [] }) {
|
|
|
879
1060
|
registra(compor(nome, br, ctx));
|
|
880
1061
|
});
|
|
881
1062
|
|
|
882
|
-
/* ── os
|
|
883
|
-
|
|
884
|
-
|
|
1063
|
+
/* ── os chãos: a identidade do vídeo (2 chãos e 1 acento) e o fundo que
|
|
1064
|
+
troca só na virada de seção, sempre entre os dois chãos. Quem começa é
|
|
1065
|
+
o fundo da marca; sem marca, a semente. ─────────────────────────────── */
|
|
885
1066
|
const ditaCor = tokens(partes.map((p) => [p.titulo, p.destaque].join(' ')).join(' ')).map((w) => CORES_DITAS[w]).find(Boolean) || null;
|
|
886
|
-
const campos = camposDe(marca, humor, s, ditaCor);
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
});
|
|
896
|
-
for (const b of batidas) { b.fundo = 'campo' + b.campo; b.cores = coresDoCampo(campos, b.campo, s); }
|
|
1067
|
+
const campos = camposDe(marca, humor, s, ditaCor, E.paleta);
|
|
1068
|
+
const ident = identidadeDe(campos, marca);
|
|
1069
|
+
// O campo continua um índice (o kit e as passagens leem): 0 = o chão escuro, 2 = o claro.
|
|
1070
|
+
campos[0] = ident.chaos[0]; campos[2] = ident.chaos[1];
|
|
1071
|
+
const fm = (marca.cores || {}).fundo;
|
|
1072
|
+
const comeca = ehCor(fm) ? (fm === campos[0] ? 0 : 2) : (s.chance(0.5) ? 0 : 2);
|
|
1073
|
+
const secoes = secoesDe(partes);
|
|
1074
|
+
batidas.forEach((b) => { b.secao = secoes.idx[b.parte] || 0; b.campo = b.secao % 2 === 0 ? comeca : 2 - comeca; });
|
|
1075
|
+
for (const b of batidas) { b.fundo = 'campo' + b.campo; b.cores = coresDoCampo(campos, b.campo, s, ident); }
|
|
897
1076
|
|
|
898
|
-
/* ──
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
1077
|
+
/* ── O HERÓI DE CADA BATIDA VEM DO CONTEÚDO (09/10): "é uma conversa sobre
|
|
1078
|
+
pão e ele vai naquela bolinha" (Joel). Quem diz é o roteiro: parte.heroi
|
|
1079
|
+
(ou parte.icone) — um ícone do Iconify ("mdi:bread-slice", o desenho
|
|
1080
|
+
chega em `icones`), um 3D do kit (raio, relogio…), uma forma abstrata
|
|
1081
|
+
pelo nome, ou "nenhum". Sem isso: o ícone 3D quando a fala cita um
|
|
1082
|
+
objeto do catálogo (ICONE_DE, sugestão), no máximo em ~45% das batidas;
|
|
1083
|
+
e a forma abstrata só no assunto abstrato, a escolhida do projeto, em
|
|
1084
|
+
até 1 de cada 3 batidas. Nada serve: a tipografia carrega a batida. ── */
|
|
1085
|
+
const pedidoDe = partes.map((p) => { const h = p.heroi != null ? p.heroi : p.icone; return typeof h === 'string' && h.trim() ? h.trim() : null; });
|
|
1086
|
+
const ABSTRATOS = [...HEROIS_OBJETO, ...HEROIS_CAMPO];
|
|
1087
|
+
const ICONES3D = new Set(Object.values(ICONE_DE));
|
|
1088
|
+
const materialSorteado = s.um(['argila', 'verniz', 'metal']);
|
|
1089
|
+
// O arranjo do projeto (escolherVisual); sem ele, o de antes: um lado só, ícones seguidos.
|
|
1090
|
+
const EI = E.icones || { arranjo: 'lado', ritmo: 'seguidos' };
|
|
1091
|
+
const material = EI.material || materialSorteado;
|
|
1092
|
+
const lado1 = deitado ? 'direita' : 'acima', lado2 = deitado ? 'esquerda' : 'abaixo';
|
|
1093
|
+
const reserva = deitado ? ['direita', 'acima', 'abaixo', 'esquerda'] : ['acima', 'abaixo', 'direita', 'esquerda'];
|
|
1094
|
+
const folga = { seguidos: 1, alternados: 2, esparsos: 3 }[EI.ritmo] || 1;
|
|
903
1095
|
const iconesUsados = new Set();
|
|
904
1096
|
const tetoIcones = Math.ceil(batidas.length * 0.45);
|
|
905
|
-
|
|
906
|
-
|
|
1097
|
+
let ultimoIcone = -99, nIcones = 0;
|
|
1098
|
+
const atendidas = new Set();
|
|
1099
|
+
// O svg do Iconify vem com 1em de lado: sai do tamanho da caixa.
|
|
1100
|
+
const svgLimpo = (svg) => String(svg).replace(/\swidth="[^"]*"/, ' width="100%"').replace(/\sheight="[^"]*"/, ' height="100%"');
|
|
1101
|
+
const porIcone = (b, ic, extra = {}) => {
|
|
1102
|
+
const primeiro = { lado: lado1, 'outro-lado': lado2, canto: 'canto', alterna: nIcones % 2 ? lado2 : lado1 }[EI.arranjo] || lado1;
|
|
1103
|
+
const lados = [primeiro, ...reserva.filter((l) => l !== primeiro)];
|
|
1104
|
+
const prim = b.linhas[0];
|
|
1105
|
+
const escala = primeiro === 'canto' ? 0.6 : 1;
|
|
1106
|
+
b.icone3d = { icone: ic, lado: primeiro, lados, tamanho: Math.round((deitado ? 0.4 * H : 0.38 * W) * escala), material,
|
|
1107
|
+
cor: b.cores.texto, cor2: b.cores.acento, luz: b.cores.acento, t: r3(Math.min(b.ate - 1, (prim ? prim.t : b.de) + 0.15)), ...extra };
|
|
1108
|
+
b.acentos += 1;
|
|
1109
|
+
if (b.acentos > REGRAS.acentos) { b.icone3d.cor2 = misturarCor(b.cores.texto, b.cores.fundo, 0.3); b.acentos -= 1; }
|
|
1110
|
+
iconesUsados.add(ic); nIcones++;
|
|
1111
|
+
};
|
|
1112
|
+
for (const [bi, b] of batidas.entries()) {
|
|
1113
|
+
const pedido = pedidoDe[b.parte];
|
|
1114
|
+
if (pedido) {
|
|
1115
|
+
// O pedido vale numa batida só da parte: a do conteúdo (a fala solta fica com a tipografia).
|
|
1116
|
+
if (b.origem !== 'conteudo' || atendidas.has(b.parte) || b.produto3d) continue;
|
|
1117
|
+
atendidas.add(b.parte);
|
|
1118
|
+
if (/^(nenhum|nenhuma|nada|none)$/i.test(pedido)) continue;
|
|
1119
|
+
if (ABSTRATOS.includes(pedido)) { b.heroiPedido = pedido; continue; }
|
|
1120
|
+
if (ICONES3D.has(pedido)) { porIcone(b, pedido); ultimoIcone = bi; continue; }
|
|
1121
|
+
if (/^[a-z0-9-]+:[a-z0-9-]+$/i.test(pedido) && icones[pedido]) {
|
|
1122
|
+
porIcone(b, pedido, { svg: svgLimpo(icones[pedido]), cor: b.cores.acento, cor2: b.cores.texto });
|
|
1123
|
+
ultimoIcone = bi;
|
|
1124
|
+
}
|
|
1125
|
+
continue;
|
|
1126
|
+
}
|
|
1127
|
+
if (nIcones >= tetoIcones) continue;
|
|
1128
|
+
if (bi - ultimoIcone < folga) continue;
|
|
907
1129
|
if (b.produto3d || ['parede', 'marca', 'citacao', 'divisao'].includes(b.comp)) continue;
|
|
908
1130
|
const ditas = tokens(b.linhas.map((l) => l.texto).join(' ') + ' ' + ((partes[b.parte] && partes[b.parte].fala) || ''));
|
|
909
1131
|
const ic = ditas.map((w) => ICONE_DE[w]).find((x) => x && !iconesUsados.has(x));
|
|
910
1132
|
if (!ic) continue;
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
b.icone3d = { icone: ic, lado: deitado ? 'direita' : 'acima', tamanho: Math.round(deitado ? 0.4 * H : 0.38 * W), material,
|
|
914
|
-
cor: b.cores.texto, cor2: b.cores.acento, luz: b.cores.acento, t: r3(Math.min(b.ate - 1, (prim ? prim.t : b.de) + 0.15)) };
|
|
915
|
-
b.acentos += 1;
|
|
916
|
-
if (b.acentos > REGRAS.acentos) { b.icone3d.cor2 = misturarCor(b.cores.texto, b.cores.fundo, 0.3); b.acentos -= 1; }
|
|
1133
|
+
porIcone(b, ic);
|
|
1134
|
+
ultimoIcone = bi;
|
|
917
1135
|
}
|
|
918
1136
|
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
Procedurais, da semente, na paleta da batida; nunca o mesmo seguido. ── */
|
|
925
|
-
const OBJETOS = ['explosao', 'poligono', 'esfera', 'toro'], CAMPOS_H = ['truchet', 'aneis', 'grade', 'rastro'];
|
|
926
|
-
const usosH = {};
|
|
927
|
-
let ultimoH = null;
|
|
928
|
-
const recentesH = [];
|
|
1137
|
+
// A forma abstrata: a que o modelo pediu, ou a do projeto no assunto que aceita.
|
|
1138
|
+
const OBJETOS = HEROIS_OBJETO;
|
|
1139
|
+
const favorito = (ABSTRATO_DO_HUMOR[humor] || 0) > 0 ? E.herois[0] : null;
|
|
1140
|
+
const tetoH = Math.ceil(batidas.length / 3);
|
|
1141
|
+
let nH = 0, ultimoH = -9;
|
|
929
1142
|
batidas.forEach((b, i) => {
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
if (recentesH.slice(-3).includes(h)) q = 0;
|
|
937
|
-
if (h === 'truchet' && (usosH.truchet || 0) >= 1) q = 0;
|
|
938
|
-
if ((h === 'esfera' || h === 'toro') && (usosH[h] || 0) >= 1) q = 0;
|
|
939
|
-
q *= Math.pow(0.5, usosH[h] || 0);
|
|
940
|
-
w[h] = q;
|
|
941
|
-
}
|
|
942
|
-
// Tudo recente: o menos recente de todos os de campo.
|
|
943
|
-
const reserva = () => CAMPOS_H.filter((x) => !recentesH.slice(-3).includes(x))[0] || CAMPOS_H[0];
|
|
944
|
-
let h = i === 0 && b.comp === 'marca' ? 'explosao' : s.pesado(w) || reserva();
|
|
945
|
-
usosH[h] = (usosH[h] || 0) + 1;
|
|
946
|
-
ultimoH = h;
|
|
947
|
-
recentesH.push(h);
|
|
1143
|
+
let h = b.heroiPedido || null;
|
|
1144
|
+
if (!h && favorito && !b.icone3d && !b.produto3d && !pedidoDe[b.parte] && nH < tetoH && i - ultimoH >= 2
|
|
1145
|
+
&& !(OBJETOS.includes(favorito) && ['gigante', 'parede', 'divisao'].includes(b.comp))) h = favorito;
|
|
1146
|
+
delete b.heroiPedido;
|
|
1147
|
+
if (!h) return;
|
|
1148
|
+
nH++; ultimoH = i;
|
|
948
1149
|
const comCor = b.acentos < REGRAS.acentos;
|
|
949
1150
|
if (comCor) b.acentos += 1;
|
|
950
1151
|
b.heroi = { tipo: h, modo: OBJETOS.includes(h) ? 'objeto' : 'campo', semente: hash(b.id + h) % 9973,
|
|
@@ -959,7 +1160,8 @@ function partituraDe({ roteiro, largura = 1920, altura = 1080, tempos = [] }) {
|
|
|
959
1160
|
const a = batidas[i - 1];
|
|
960
1161
|
let tr;
|
|
961
1162
|
// O campo troca por corte seco, por cortina ou pela inundação do acento — independente do gesto do texto.
|
|
962
|
-
|
|
1163
|
+
// A primeira virada de seção é um corte seco (o reel corta); as outras, sorteadas.
|
|
1164
|
+
b.troca = a.campo === b.campo ? null : !batidas.slice(1, i).some((x) => x.troca) ? 'corte' : s.pesado({ corte: 5, cortina: 2.5, inunda: 1.5 });
|
|
963
1165
|
if (b.troca === 'inunda' && s.chance(0.5)) tr = 'inunda';
|
|
964
1166
|
else {
|
|
965
1167
|
const tese = b.linhas.find((l) => l.papel === 'tese' || l.papel === 'numero');
|
|
@@ -1067,13 +1269,34 @@ function partituraDe({ roteiro, largura = 1920, altura = 1080, tempos = [] }) {
|
|
|
1067
1269
|
// Partículas em três profundidades: o espaço respira mesmo no repouso.
|
|
1068
1270
|
particulas: Array.from({ length: 34 }, (_, i) => ({ x: r1(s.prox() * W), y: r1(s.prox() * H), z: [0.25, 0.5, 0.85][i % 3], r: r1((1.2 + s.prox() * 2.4) * base), v: r1(s.entre(6, 22) * base) })) };
|
|
1069
1271
|
if (roteiro.particulas !== true) fundo.particulas = [];
|
|
1272
|
+
// O ambiente e o jeito do acento: o roteiro, a escolha do projeto, ou o limpo e o traço.
|
|
1273
|
+
fundo.ambiente = AMBIENTES.includes(roteiro.ambiente) ? roteiro.ambiente : E.ambiente || 'limpo';
|
|
1274
|
+
fundo.acento = ACENTOS_JEITO.includes(roteiro.acento) ? roteiro.acento : E.acento || 'traco';
|
|
1070
1275
|
|
|
1071
1276
|
return {
|
|
1072
1277
|
versao: 1, semente, fps: FPS, humor,
|
|
1073
|
-
musica: { genero: s.um(H0.generos), generos: H0.generos }, largura: W, altura: H, duracao, regras: REGRAS,
|
|
1278
|
+
musica: { genero: ou(E.genero, s.um(H0.generos)), generos: H0.generos }, largura: W, altura: H, duracao, regras: REGRAS,
|
|
1074
1279
|
paleta: { ...P, fundo: campos[0], tinta: campos[2], acento: campos[1] }, campos, fontes, eixos: Object.fromEntries(Object.values(fontes).map((f) => [f, (VARIAVEIS[f] || {}).eixos || { wght: [400, 700] }])),
|
|
1075
1280
|
margem: M, fundo, batidas, camera, produtos,
|
|
1076
|
-
|
|
1281
|
+
identidade: { chaos: ident.chaos, acento: ident.acento, comeca: campos[comeca], fontes: [display, texto] }, secoes: secoes.nomes,
|
|
1282
|
+
hud: molduraDe(roteiro, E, { W, H, deitado, marca, secoes: secoes.nomes }),
|
|
1283
|
+
};
|
|
1284
|
+
}
|
|
1285
|
+
|
|
1286
|
+
/* A moldura do vídeo: o pedido do roteiro (moldura, ou o hud antigo) manda;
|
|
1287
|
+
sem pedido, a escolha do projeto; sem as duas, nenhuma. A ÁREA SEGURA: no
|
|
1288
|
+
9:16 o celular cobre o alto (status, nome do perfil) e o pé (legenda,
|
|
1289
|
+
botões) — a moldura fica dentro; no 16:9, margem fina e igual. */
|
|
1290
|
+
function molduraDe(roteiro, E, { W, H, deitado, marca, secoes }) {
|
|
1291
|
+
const m = roteiro.moldura;
|
|
1292
|
+
const tipo = m === false ? 'sem' : m === true ? 'completa' : MOLDURAS.includes(m) ? m : roteiro.hud === true ? 'completa' : E.moldura || 'sem';
|
|
1293
|
+
if (tipo === 'sem') return null;
|
|
1294
|
+
const assunto = String(marca.nome || roteiro.titulo || 'PrimoCode').split(/\s+[—–-]\s+/)[0];
|
|
1295
|
+
const d = Math.round(Math.min(W, H) * 0.04);
|
|
1296
|
+
return {
|
|
1297
|
+
tipo, marca: assunto.toUpperCase().slice(0, deitado ? 32 : 22), ano: '2026',
|
|
1298
|
+
secoes: secoes.map((n, i) => (String(n || '').toUpperCase().slice(0, deitado ? 26 : 16) || 'PARTE ' + (i + 1))),
|
|
1299
|
+
area: deitado ? { x: d, topo: d, base: d } : { x: Math.round(W * 0.06), topo: Math.round(H * 0.08), base: Math.round(H * 0.15) },
|
|
1077
1300
|
};
|
|
1078
1301
|
}
|
|
1079
1302
|
|
|
@@ -1290,7 +1513,8 @@ function compor(comp, br, ctx) {
|
|
|
1290
1513
|
b.bloco = { x: W / 2, y: H * 0.5, alinha: 'centro', vert: 'meio', largura: larguraUtil, gap: 0.1 };
|
|
1291
1514
|
linha(txt, 'tese', { fonte, tamanho: tam, eixos: { wght: Math.min(900, VARIAVEIS[fonte].eixos.wght[1]) }, maiusc });
|
|
1292
1515
|
b.textura = { texto: (maiusc ? txt.toUpperCase() : txt), tamanho: tam, fonte, linhas: Math.ceil(H / (tam * 1.02)) + 3, velocidade: Math.round(s.entre(40, 90) * base) };
|
|
1293
|
-
|
|
1516
|
+
// Moldura em volta da palavra é vício de "cara de IA" (a caixa na palavra-chave); sai em 08/10.
|
|
1517
|
+
b.acento.push({ forma: s.chance(0.5) ? 'ponto' : 'sublinha', alvo: { linha: b.linhas[0].id } });
|
|
1294
1518
|
break;
|
|
1295
1519
|
}
|
|
1296
1520
|
default: throw new Error('composição desconhecida: ' + comp);
|
|
@@ -1466,6 +1690,6 @@ function contarAcentos(b) {
|
|
|
1466
1690
|
}
|
|
1467
1691
|
|
|
1468
1692
|
module.exports = {
|
|
1469
|
-
montarGerativo, partituraDe, sonsDoVideo, musicaDe, desenharSom, humorDe, corGenerica, BANIDAS, HUMORES, REGRAS, VARIAVEIS, COMPOSICOES, COMPORTAMENTOS, SAIDAS, ACENTOS, FPS,
|
|
1470
|
-
especEixos, sorteador, hash, larguraEm, conteudoDe, casar,
|
|
1693
|
+
montarGerativo, partituraDe, sonsDoVideo, musicaDe, desenharSom, humorDe, escolherVisual, MOLDURAS, AMBIENTES, ACENTOS_JEITO, ARRANJOS_ICONE, RITMOS_ICONE, MATERIAIS_ICONE, corGenerica, BANIDAS, HUMORES, REGRAS, VARIAVEIS, COMPOSICOES, COMPORTAMENTOS, SAIDAS, ACENTOS, FPS,
|
|
1694
|
+
especEixos, sorteador, hash, larguraEm, conteudoDe, casar, HEROIS_OBJETO, HEROIS_CAMPO,
|
|
1471
1695
|
};
|
package/lib/estudio/hibrido.js
CHANGED
|
@@ -49,7 +49,7 @@ function traduzir(roteiro) {
|
|
|
49
49
|
partes.forEach((p, i) => {
|
|
50
50
|
const tipo = p.tipo || 'frase';
|
|
51
51
|
const marcar = [p.destaque, ...(p.marcar || [])].filter(Boolean);
|
|
52
|
-
const base = { fala: p.fala, ...(p.mudo ? { mudo: true } : {}), ...(p.falaForte ? { falaForte: p.falaForte } : {}), marcar };
|
|
52
|
+
const base = { fala: p.fala, ...(p.mudo ? { mudo: true } : {}), ...(p.falaForte ? { falaForte: p.falaForte } : {}), ...(p.falante ? { falante: p.falante } : {}), marcar };
|
|
53
53
|
let q;
|
|
54
54
|
// Os tipos que o cinema já conhece passam como estão.
|
|
55
55
|
if (['frase', 'montagem', 'numeros', 'escada', 'dia', 'depoimento', 'climax', 'final'].includes(tipo)) q = { ...p };
|