primocode 10.9.1 → 10.10.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/lib/act.js +2 -0
- package/lib/catalogo.js +6 -2
- package/lib/comandos-extra.js +2 -1
- package/lib/entrada.js +18 -0
- package/lib/estudio/cena.js +47 -0
- package/lib/estudio/estilos.js +147 -0
- package/lib/estudio/gerativo.js +104 -6
- package/lib/estudio/index.js +139 -11
- package/lib/estudio/kit/gerativo.jsx +22 -2
- package/lib/estudio/referencias.js +9 -2
- package/lib/tools.js +15 -2
- package/lib/ui.js +14 -1
- package/lib/video/cor.js +104 -0
- package/lib/video/filters.js +3 -0
- package/lib/video/ops.js +31 -0
- package/package.json +2 -2
- package/skills/motion-de-primeira/SKILL.md +35 -36
- package/studio/edge.py +5 -0
- package/studio/narrar.py +16 -0
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# PrimoCode v10.
|
|
1
|
+
# PrimoCode v10.10.1
|
|
2
2
|
|
|
3
3
|
Agente de engenharia brasileiro para o terminal, no estilo Claude Code.
|
|
4
4
|
Cria arquivos de verdade, roda comandos, **controla o navegador e o desktop** —
|
|
@@ -9,7 +9,7 @@ OpenRouter, e cada apelido (`top`/`main`/`fast`) é uma **corrente**: se um mode
|
|
|
9
9
|
está no limite de pedidos, o próximo atende. Você não vê o erro, e não paga nada.
|
|
10
10
|
|
|
11
11
|
```
|
|
12
|
-
▐▛███▜▌ Primo Code v10.
|
|
12
|
+
▐▛███▜▌ Primo Code v10.10.1
|
|
13
13
|
▝▜█████▛▘ Bem-vindo de volta, Joel
|
|
14
14
|
▘▘ ▝▝ ~/primocode · /dir <pasta> muda
|
|
15
15
|
```
|
package/lib/act.js
CHANGED
|
@@ -939,6 +939,8 @@ async function runAct(userPrompt, opts) {
|
|
|
939
939
|
+ 'Senão: '
|
|
940
940
|
+ 'rode `primocode video help` UMA vez (lista as operações), depois import → remove_silence/remove_words → add_captions → inspect (olhe o png) → export. '
|
|
941
941
|
+ 'Cada edição devolve um delta curto e tem undo: edite sem pedir licença a cada passo.\n'
|
|
942
|
+
/* "deixar os vídeos cinematográficos… fazer um color grading sozinha" (13/10). */
|
|
943
|
+
+ 'CINEMATOGRÁFICO: `primocode video grade --json \'{}\'` mede a imagem e faz o color grading de cinema sozinho (look:cinema|quente|noite|natural|pb, intensidade); use antes do export quando a pessoa quer o vídeo bonito, e OLHE o inspect.\n'
|
|
942
944
|
+ 'EFEITOS que seguem a pessoa: separate_layers (recorte + momento 3D), replace_background (fundo novo no estalo), '
|
|
943
945
|
+ 'attach_3d (logo 3D na mão, segue a mão), merge_on_join (mãos juntas → fusão → cérebro), cut_silence_at (silêncio depois do estalo), '
|
|
944
946
|
+ 'style_captions (cor, legenda em cima da cabeça) — detalhe em `primocode video help efeitos`. Atalho: `primocode video do --json \'{"pedido":"<o pedido em português>"}\'`.' });
|
package/lib/catalogo.js
CHANGED
|
@@ -93,7 +93,8 @@ const GRUPOS = [
|
|
|
93
93
|
+ 'contraste{nao,sim} citacao{texto,autor} imagem{busca,titulo,sub} grafico{titulo,barras:[{rotulo,valor}]} cta{titulo,link} '
|
|
94
94
|
+ 'logo{titulo} cores{cores:[{nome,hex}]} produto{titulo,texto,destaque,selo,barras}. Em qualquer parte: destaque (a palavra-tese), fonte (a fonte citada). O texto da TELA sai da FALA: o título usa palavras que a fala diz, cedo na frase (a tela entra quando a voz diz; palavra que a voz não diz deixa a tela esperando). '
|
|
95
95
|
+ 'visual:{titulo,texto,paleta} (hibrido, cinema, impacto): fonte e cor do cardápio (estudio_midia acao:fontes); sem visual, o PrimoCode sorteia sem repetir os últimos vídeos. '
|
|
96
|
-
+ '
|
|
96
|
+
+ 'componente:"componentes/x.jsx" numa parte = a cena que VOCÊ escreveu em React — é o caminho do motion (skill Motion de primeira): uma ideia desenhada por fala; a voz, a trilha, os sons e o fundo seguem do Estúdio; cena que não compila volta em cenasQuebradas. '
|
|
97
|
+
+ 'estilo (deixe VAZIO: o gerativo, com as suas cenas em componente e o estiloVisual que você montou; outro só se a pessoa pedir pelo nome): hibrido (SÓ quando a pessoa pediu imagem: foto e vídeo reais + o motion por cima; dê busca:"termo em inglês" só nas partes em que a imagem agrega — a COISA que a frase nomeia, 2 a 4 palavras, não a frase inteira; rota:"tipografia" em número/citação, "produto" quando a frase fala do produto; ancora:"palavra" — o clipe troca 0,2 s depois dela) | gerativo (= motion: sem template — a tipografia anima nos eixos da fonte numa linha do tempo contínua, composição, fonte, cor e som — estiloVisual {humor, paleta, display, texto, mono, genero, ambiente, acento, material} é o estilo que VOCÊ monta pelo assunto (cardápio: estudio_midia acao:estilos; vazio, o PrimoCode sorteia); modelo3d fone|celular|notebook|relogio|garrafa|lata|caixa: o objeto gira em 3D numa batida — ponha quando o vídeo é de um produto ou objeto físico; ideia abstrata, história ou explicação: deixe vazio; o vídeo tem UMA identidade (2 chãos, 1 acento, 2 fontes) e o fundo só troca na virada de seção — se o roteiro tem atos, dê secao:"nome curto" nas partes, senão o PrimoCode divide; moldura cantos|completa: o quadro fixo do reel (cantos, o assunto no alto, "01 · seção", o tempo) — SÓ quando a pessoa pedir; vazio = sem moldura (ela entrega que o vídeo foi gerado); heroi em cada parte: o desenho da batida além do texto, tirado do CONTEÚDO — um ícone 3D RENDERIZADO da biblioteca ("fluent3d:hot beverage", "fluent3d:money bag"; procure com estudio_midia acao:icones3d — é o que mais parece 3D de verdade), uma animação pronta ("noto:1f525"; acao:animacoes), um ícone do Iconify do objeto que a fala cita ("mdi:bread-slice", "mdi:qrcode"; acao:icones), um 3D do kit (raio|relogio|check|balao|escudo|coracao|estrela|sol|play|engrenagem|cadeado|barras|fone|onda|celular|seta|bateria) ou "nenhum" quando a tipografia basta; forma abstrata (explosao|poligono|esfera|toro|truchet|aneis|grade|rastro) só em assunto abstrato ou de tecnologia — numa conversa de padaria, nunca; ambiente limpo|luz|bokeh e acento traco|viagem (o ponto que viaja entre as batidas): vazio, o PrimoCode escolhe com memória e o bokeh quase não sai fora de tecnologia) | classico (o motion 3D antigo) | vox (explainer editorial: tinta sobre papel, câmera que conta a frase) | '
|
|
97
98
|
+ 'marca (brand reveal: brilho, letras em vetor, marca cromada, padrão) | lancamento (produto: cartões, painel, calendário) | fluido (minimalista: uma ideia por batida, a forma de uma vira a próxima, sem corte; paleta noite|breu|preto|cobalto (escuras) ou lavanda|creme|laranja|branco (claras) — escolha pelo assunto e NÃO repita a do vídeo anterior; vazio, o PrimoCode escolhe; fontesFluido aprovado|editorial|revista|carater|tecnico|classico, vazio gira) | impacto | cinema | personagem (a ANIMAÇÃO 3D de verdade, o vídeo GRAVADO em 3D no visual de blocos/voxel de render de cinema — é a escolha para animação 3D, personagem, bicho, boneco, avatar, mascote, podcast, entrevista ou alguém FALANDO em 3D; cada parte é UMA fala {falante, texto, emocao?, destaque?, acao?, cenario?}, sem tipo). NADA É FIXO, monte o que a pessoa pediu: cena:"texto livre do lugar" (praia, cidade, floresta, parque, fazenda, neve, deserto, espaço, fundo do mar, escritório, sala, cozinha, escola, palco… + hora: "à noite", "ao pôr do sol") ou cena:{lugar, hora, elementos:["palmeira","barraca","violão",…]} — elemento fora do catálogo vira ícone 3D em blocos; parte.cenario muda o lugar a partir daquela fala. Até 4 falantes; personagens:{"nome":{tipo:pessoa|robo|animal|coisa, especie (animal: cachorro|gato|cavalo|vaca|porco|ovelha|raposa|lobo|urso|panda|coelho|leao|tigre|elefante|girafa|tartaruga|galinha|pato|passaro|papagaio|coruja|pinguim|peixe|tubarao|golfinho|dinossauro|sapo|macaco), icone (coisa: "pizza", "café", "polvo" — qualquer objeto/bicho que fala), pessoa: pele, cabelo:curto|cacheado|longo|topete|coque|careca, corCabelo, roupa, oculos, barba; robo: cor}} — sem tipo, o nome decide (nome de bicho vira bicho, de IA vira o mascote laranja, de objeto vira coisa); figurantes:["cachorro","pessoa"] (quem está na cena sem falar); BICHO NÃO FALA: a fala de um bicho sai da narração e ele fica na cena — só fala se a pessoa pedir ("cachorro falante") ou personagens.x.fala:true; a voz de cada falante sai do GÊNERO do nome (personagens.x.genero:f|m manda), uma voz por personagem do começo ao fim; bigode:true e cabeca:"cubo" (o rosto de pixel antigo) em personagens; sem lugar no pedido, o set é a cobertura à noite (janela da cidade, cartões de interface na frente). acao em cada parte: acenar|apontar|pular|dancar|comemorar|pensar|andar|correr. emocao feliz|serio|surpreso|pensativo|animado|curioso; destaque:"texto curto"; paineis:["título",…] só no set de telas (o padrão quando o pedido não diz lugar); visual:"liso" só se pedirem o boneco liso antigo). '
|
|
98
99
|
+ 'marca e lancamento aceitam visual:orbe|vetor|esfera|grade|site|padrao|cromo|cartoes|painel|cores numa parte. marca.nome dá o nome. '
|
|
99
100
|
+ 'voz (thalita|antonio|francisca). DUAS VOZES (o pedido fala em dois narradores, diálogo, conversa, entrevista): roteiro.vozes {"nome":"antonio","outro":"thalita"} e falante:"nome" em cada parte — cada um sai na sua voz. Ajuste fino: plano: {formato:{largura,altura,fps}, cenas:[…], audio:{…}} — ou cenas direto. '
|
|
@@ -109,8 +110,11 @@ const GRUPOS = [
|
|
|
109
110
|
+ 'o render empurra para dentro, encolhe o texto largo e afasta o colado; enfeite de fundo marque decorativo: true.' },
|
|
110
111
|
|
|
111
112
|
{ tool: 'estudio_midia', escreve: true, faz: 'procura e baixa ícone, imagem, música e efeito sonoro da internet, e lista as fontes',
|
|
112
|
-
args: 'acao: biblioteca | movimentos | cenarios | salvar_cenario | imagem | icones | icones3d | animacoes | som | trilha | efeito | fontes | print | elementos | elemento | recortar. termo. id (para baixar). '
|
|
113
|
+
args: 'acao: biblioteca | estilos | salvar_estilo | movimentos | cenarios | salvar_cenario | imagem | icones | icones3d | animacoes | som | trilha | efeito | fontes | print | elementos | elemento | recortar. termo. id (para baixar). '
|
|
113
114
|
+ 'movimentos termo:"quique" categoria:entrada|saida|enfase|loop: os 100+ movimentos prontos com nome; no elemento, movimento:"<nome>" (ou {nome, em, duracao}, ou uma lista) vira quadros-chave sozinho. Varie: não use o mesmo em todo elemento. Pode inventar o seu em anima. '
|
|
115
|
+
+ 'estilos: as peças do gerativo (humor, paleta, fontes, trilha, ambiente, acento, material) — dezenas de milhares de combinações, e os estilos que a pessoa salvou. '
|
|
116
|
+
+ 'Monte o do vídeo pelo ASSUNTO em roteiro.estiloVisual; misture um salvo com {base:"<nome>", ...}; crie paleta nova com 5 cores. '
|
|
117
|
+
+ 'No fim, pergunte "posso salvar este estilo pra usar nos próximos vídeos?" e, SÓ com o sim, salvar_estilo com nome, pecas {…} e autorizado:true. '
|
|
114
118
|
+ 'cenarios: os lugares prontos do vídeo 3D e os que a pessoa já salvou. Pode CRIAR um cenário novo na cena (lugar base + ceu, chao, parede, luz, acento, hora, elementos); '
|
|
115
119
|
+ 'no fim do vídeo pergunte "posso salvar este cenário pra usar na próxima vez?" e, SÓ com o sim, salvar_cenario com nome, cenario {…} e autorizado:true. Depois, lugar:"<nome>" traz ele de volta. '
|
|
116
120
|
+ 'biblioteca: o cardápio INTEIRO do que existe (ícone 2D, ícone 3D, animação pronta, efeito sonoro, música, fonte, personagem 3D) — olhe antes de escolher. '
|
package/lib/comandos-extra.js
CHANGED
|
@@ -466,7 +466,8 @@ const ATALHOS = [
|
|
|
466
466
|
['Ctrl+C', 'limpa o que foi digitado; com a caixa vazia, sai'],
|
|
467
467
|
['Ctrl+D', 'sai (com a caixa vazia)'],
|
|
468
468
|
['Ctrl+L', 'limpa a tela'],
|
|
469
|
-
['
|
|
469
|
+
['↑ ↓ · roda · PgUp PgDn', 'rolam a conversa (na tela cheia)'],
|
|
470
|
+
['Option+↑ · Option+↓', 'o pedido anterior · o próximo (na tela cheia)'],
|
|
470
471
|
['arrastar o mouse', 'seleciona texto; Cmd+C (Mac) ou Ctrl+Shift+C copia'],
|
|
471
472
|
['Ctrl+A · Ctrl+E', 'vai para o começo · o fim da linha'],
|
|
472
473
|
['Ctrl+U · Ctrl+K', 'apaga antes · depois do cursor'],
|
package/lib/entrada.js
CHANGED
|
@@ -587,6 +587,20 @@ class Entrada extends EventEmitter {
|
|
|
587
587
|
this.desenhado = { linhas: q.linhas, cursorLinha: q.cursor.linha };
|
|
588
588
|
}
|
|
589
589
|
|
|
590
|
+
/* A RODA QUE VIRAVA HISTÓRICO (13 e 14/10: "quando eu tento subir o
|
|
591
|
+
histórico ele fica mostrando meus prompts"). Sem capturar o mouse (por
|
|
592
|
+
causa da seleção de texto), o terminal manda a roda como setas ↑↓. O
|
|
593
|
+
ritmo não separa as duas coisas: medido no Orca (14/10), a roda devagar
|
|
594
|
+
chega uma seta a cada 50–250 ms, e o ↑ segurado no teclado a cada 83 ms.
|
|
595
|
+
Então, na tela cheia, a seta ROLA a conversa (2 linhas), sempre; o
|
|
596
|
+
pedido anterior fica no Option+↑/↓ (ou Ctrl/Shift, o que o terminal mandar). */
|
|
597
|
+
_setaNaCheia(dir, key) {
|
|
598
|
+
// ESC ESC [A é o Option dos terminais com "Option como Meta": o Node entrega como seta pura.
|
|
599
|
+
const comOption = key && (key.meta || key.ctrl || key.shift || String(key.sequence || '').startsWith('\x1b\x1b'));
|
|
600
|
+
if (comOption) return this._historico(dir > 0 ? -1 : 1);
|
|
601
|
+
return this._rolar(dir * 2);
|
|
602
|
+
}
|
|
603
|
+
|
|
590
604
|
_rolar(passo) {
|
|
591
605
|
this.rolagem = Math.max(0, this.rolagem + passo);
|
|
592
606
|
this._pintar();
|
|
@@ -878,12 +892,14 @@ class Entrada extends EventEmitter {
|
|
|
878
892
|
|
|
879
893
|
case 'up':
|
|
880
894
|
if (this.menu) { this.menu.sel = Math.max(0, this.menu.sel - 1); return this._redesenhar(); }
|
|
895
|
+
if (this.cheia) return this._setaNaCheia(1, key);
|
|
881
896
|
return this._historico(-1);
|
|
882
897
|
case 'down':
|
|
883
898
|
if (this.menu) {
|
|
884
899
|
this.menu.sel = Math.min(this.menu.itens.length - 1, this.menu.sel + 1);
|
|
885
900
|
return this._redesenhar();
|
|
886
901
|
}
|
|
902
|
+
if (this.cheia) return this._setaNaCheia(-1, key);
|
|
887
903
|
return this._historico(1);
|
|
888
904
|
|
|
889
905
|
default:
|
|
@@ -893,6 +909,8 @@ class Entrada extends EventEmitter {
|
|
|
893
909
|
|
|
894
910
|
_ctrl(key) {
|
|
895
911
|
switch (key.name) {
|
|
912
|
+
case 'up': return this.cheia ? this._setaNaCheia(1, key) : this._historico(-1);
|
|
913
|
+
case 'down': return this.cheia ? this._setaNaCheia(-1, key) : this._historico(1);
|
|
896
914
|
case 'c':
|
|
897
915
|
// Com texto no campo, Ctrl+C limpa o que foi digitado — sair
|
|
898
916
|
// por acidente no meio de um pedido longo é perder o pedido.
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* cena.js — confere a cena que a IA escreveu antes do render.
|
|
3
|
+
*
|
|
4
|
+
* "só quero motions nesse nível" (Joel, 14/10, com as referências: cada
|
|
5
|
+
* cena é uma ideia desenhada para a fala, escrita pelo modelo).
|
|
6
|
+
*
|
|
7
|
+
* O caminho do motion é a IA escrever cada cena em React (parte.componente).
|
|
8
|
+
* Um erro de sintaxe num .jsx derrubava o bundle do Remotion e o vídeo
|
|
9
|
+
* inteiro. Aqui a cena é compilada (o esbuild que já mora no remotion-base)
|
|
10
|
+
* e os imports são conferidos: a cena quebrada sai, a parte cai no gerativo,
|
|
11
|
+
* e o erro volta para a IA consertar e mandar de novo.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
'use strict';
|
|
15
|
+
|
|
16
|
+
const fs = require('fs');
|
|
17
|
+
const os = require('os');
|
|
18
|
+
const path = require('path');
|
|
19
|
+
|
|
20
|
+
const PERMITIDOS = /^(react|remotion|three|@remotion\/[\w-]+|\.{1,2}\/.+)$/;
|
|
21
|
+
|
|
22
|
+
let esbuild;
|
|
23
|
+
function compilador() {
|
|
24
|
+
if (esbuild !== undefined) return esbuild;
|
|
25
|
+
try { esbuild = require(path.join(os.homedir(), '.primocode', 'studio', 'remotion-base', 'node_modules', 'esbuild')); } catch { esbuild = null; }
|
|
26
|
+
return esbuild;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** {ok} ou {ok:false, erro} — o erro em uma linha, com a linha do arquivo. */
|
|
30
|
+
function conferirCena(arquivo, { compilar = compilador() } = {}) {
|
|
31
|
+
let codigo;
|
|
32
|
+
try { codigo = fs.readFileSync(arquivo, 'utf8'); } catch (e) { return { ok: false, erro: `não li ${path.basename(arquivo)}: ${e.message}` }; }
|
|
33
|
+
if (!/export\s+default\b/.test(codigo)) return { ok: false, erro: 'falta o export default da função da cena ({el, t, fps}).' };
|
|
34
|
+
const fora = [...codigo.matchAll(/\bfrom\s+['"]([^'"]+)['"]|\brequire\(\s*['"]([^'"]+)['"]\s*\)/g)].map((m) => m[1] || m[2]).filter((x) => !PERMITIDOS.test(x));
|
|
35
|
+
if (fora.length) return { ok: false, erro: `import fora do permitido: ${[...new Set(fora)].join(', ')} (só react, remotion, @remotion/*, three).` };
|
|
36
|
+
if (!compilar) return { ok: true, semCompilador: true };
|
|
37
|
+
try {
|
|
38
|
+
compilar.transformSync(codigo, { loader: /\.tsx$/i.test(arquivo) ? 'tsx' : 'jsx', jsx: 'automatic', logLevel: 'silent' });
|
|
39
|
+
return { ok: true };
|
|
40
|
+
} catch (e) {
|
|
41
|
+
const x = (e.errors && e.errors[0]) || {};
|
|
42
|
+
const onde = x.location ? `linha ${x.location.line}: ` : '';
|
|
43
|
+
return { ok: false, erro: `${onde}${x.text || String(e.message).split('\n')[0]}` };
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
module.exports = { conferirCena, PERMITIDOS };
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* estilos.js — os milhares de estilos do gerativo, montados por peça.
|
|
3
|
+
*
|
|
4
|
+
* "volta pro remotion, terão 10 mil estilos e a IA pode misturar eles e
|
|
5
|
+
* criar novos." (Joel, 14/10)
|
|
6
|
+
*
|
|
7
|
+
* Um estilo é uma combinação de peças: humor, paleta (o sistema de 5 campos),
|
|
8
|
+
* fonte de título, de texto e mono, gênero da trilha, ambiente, acento e o
|
|
9
|
+
* material dos ícones. As peças são as do gerativo.js, então a conta sai do
|
|
10
|
+
* código (total()), e não de uma lista escrita à mão.
|
|
11
|
+
*
|
|
12
|
+
* Quem escolhe é a IA: ela pede o cardápio (estudio_midia acao:"estilos"),
|
|
13
|
+
* monta o estilo pelo assunto e passa roteiro.estiloVisual. O que ela deixar
|
|
14
|
+
* vazio, o sorteio com memória completa (escolherVisual). Ela também pode
|
|
15
|
+
* misturar um estilo salvo (base:"<nome>") e criar uma paleta nova (5 cores).
|
|
16
|
+
* O estilo que ficou bom só é guardado com o sim da pessoa, como os cenários.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
'use strict';
|
|
20
|
+
|
|
21
|
+
const fs = require('fs');
|
|
22
|
+
const os = require('os');
|
|
23
|
+
const path = require('path');
|
|
24
|
+
const gerativo = require('./gerativo.js');
|
|
25
|
+
const { chave } = require('./cenarios.js');
|
|
26
|
+
|
|
27
|
+
const arquivo = () => path.join(os.homedir(), '.primocode', 'estilos.json');
|
|
28
|
+
const ehCor = (c) => typeof c === 'string' && /^#[0-9a-f]{6}$/i.test(c);
|
|
29
|
+
const unicos = (campo) => [...new Set(Object.values(gerativo.HUMORES).flatMap((h) => h[campo]))];
|
|
30
|
+
|
|
31
|
+
/** As paletas com nome: "comida-1" é o primeiro sistema de 5 campos do humor comida. */
|
|
32
|
+
function paletas() {
|
|
33
|
+
const out = {};
|
|
34
|
+
for (const [h, lista] of Object.entries(gerativo.CAMPOS)) lista.forEach((k, i) => { out[`${h}-${i + 1}`] = k; });
|
|
35
|
+
return out;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function pecas() {
|
|
39
|
+
return {
|
|
40
|
+
humor: Object.keys(gerativo.HUMORES),
|
|
41
|
+
paleta: Object.keys(paletas()),
|
|
42
|
+
display: unicos('displays').filter((f) => !gerativo.BANIDAS.includes(f) && gerativo.VARIAVEIS[f]),
|
|
43
|
+
texto: unicos('textos'),
|
|
44
|
+
mono: unicos('monos'),
|
|
45
|
+
genero: unicos('generos'),
|
|
46
|
+
ambiente: gerativo.AMBIENTES,
|
|
47
|
+
acento: gerativo.ACENTOS_JEITO,
|
|
48
|
+
material: gerativo.MATERIAIS_ICONE,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Quantos estilos dá pra montar só com as peças prontas (sem contar as paletas que a IA cria). */
|
|
53
|
+
function total() {
|
|
54
|
+
return Object.values(pecas()).reduce((n, lista) => n * lista.length, 1);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Confere as peças que a IA escolheu. Peça que não existe vira aviso, com as
|
|
59
|
+
* opções, e não entra (o sorteio cobre). A paleta pode ser um nome ou 5 cores.
|
|
60
|
+
*/
|
|
61
|
+
function validar(pedido) {
|
|
62
|
+
const P = pecas(), out = {}, avisos = [];
|
|
63
|
+
for (const [campo, v] of Object.entries(pedido || {})) {
|
|
64
|
+
if (campo === 'base' || v == null || v === '') continue;
|
|
65
|
+
if (campo === 'paleta') {
|
|
66
|
+
if (Array.isArray(v)) {
|
|
67
|
+
if (v.length !== 5 || !v.every(ehCor)) avisos.push('paleta nova precisa de 5 cores #rrggbb: escura, assinatura, papel e duas ousadas.');
|
|
68
|
+
else if (v.some(gerativo.corGenerica)) avisos.push(`paleta ${v.join(' ')}: tem cor de cara de IA (violeta de SaaS ou neon); troque.`);
|
|
69
|
+
else out.paleta = v.map((c) => c.toUpperCase());
|
|
70
|
+
} else if (paletas()[v]) out.paleta = paletas()[v];
|
|
71
|
+
else avisos.push(`paleta "${v}" não existe: ${P.paleta.join(', ')} ou 5 cores suas.`);
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
if (!P[campo]) { avisos.push(`"${campo}" não é peça de estilo: ${Object.keys(P).join(', ')}.`); continue; }
|
|
75
|
+
if (P[campo].includes(v)) out[campo] = v;
|
|
76
|
+
else avisos.push(`${campo} "${v}" não existe: ${P[campo].join(', ')}.`);
|
|
77
|
+
}
|
|
78
|
+
return { pecas: out, avisos };
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* O estilo do vídeo a partir do que a IA pediu (objeto, ou o nome de um salvo).
|
|
83
|
+
* Devolve as escolhasGerativo prontas: o que ela não disse, o sorteio completa.
|
|
84
|
+
*/
|
|
85
|
+
function montar(pedido, roteiro = {}, { aleatorio } = {}) {
|
|
86
|
+
let p = typeof pedido === 'string' ? { base: pedido } : { ...(pedido || {}) };
|
|
87
|
+
const avisos = [];
|
|
88
|
+
if (p.base) {
|
|
89
|
+
const salvo = ler(p.base);
|
|
90
|
+
if (salvo) p = { ...salvo.pecas, ...p };
|
|
91
|
+
else avisos.push(`não existe estilo salvo "${p.base}".`);
|
|
92
|
+
}
|
|
93
|
+
const v = validar(p);
|
|
94
|
+
avisos.push(...v.avisos);
|
|
95
|
+
const humor = v.pecas.humor;
|
|
96
|
+
const sorteio = gerativo.escolherVisual(humor ? { ...roteiro, pedido: `${roteiro.pedido || ''} ${gerativo.HUMORES[humor].palavras}` } : roteiro, { aleatorio });
|
|
97
|
+
const escolhas = { ...sorteio };
|
|
98
|
+
for (const campo of ['humor', 'paleta', 'display', 'texto', 'mono', 'genero', 'ambiente', 'acento']) if (v.pecas[campo]) escolhas[campo] = v.pecas[campo];
|
|
99
|
+
if (v.pecas.material) escolhas.icones = { ...(sorteio.icones || {}), material: v.pecas.material };
|
|
100
|
+
return { escolhas, escolhidas: Object.keys(v.pecas), avisos };
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function lerTudo() {
|
|
104
|
+
try { const j = JSON.parse(fs.readFileSync(arquivo(), 'utf8')); return j && typeof j === 'object' ? j : {}; } catch { return {}; }
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function ler(nome) {
|
|
108
|
+
const k = chave(nome);
|
|
109
|
+
return k ? lerTudo()[k] || null : null;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function listar() {
|
|
113
|
+
return Object.entries(lerTudo()).map(([k, e]) => ({ chave: k, nome: e.nome, descricao: e.descricao || '', pecas: e.pecas }));
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** Guarda o estilo. `autorizado` é a resposta da pessoa: sem ela, nada é escrito. */
|
|
117
|
+
function salvar({ nome, pecas: pedido, descricao, autorizado } = {}) {
|
|
118
|
+
if (autorizado !== true) {
|
|
119
|
+
return { ok: false, error: 'falta o sim da pessoa.', comoResolver: 'pergunte "posso salvar este estilo pra usar nos próximos vídeos?" e só chame de novo com autorizado:true se ela disser que sim.' };
|
|
120
|
+
}
|
|
121
|
+
const k = chave(nome);
|
|
122
|
+
if (!k) return { ok: false, error: 'dê um nome ao estilo (ex: "padaria de manhã").' };
|
|
123
|
+
const v = validar(pedido);
|
|
124
|
+
if (!Object.keys(v.pecas).length) return { ok: false, error: 'nenhuma peça válida para guardar.', avisos: v.avisos };
|
|
125
|
+
const tudo = lerTudo();
|
|
126
|
+
const novo = { nome: String(nome).slice(0, 60), pecas: v.pecas, salvo: new Date().toISOString() };
|
|
127
|
+
if (typeof descricao === 'string' && descricao.trim()) novo.descricao = descricao.slice(0, 200);
|
|
128
|
+
tudo[k] = novo;
|
|
129
|
+
fs.mkdirSync(path.dirname(arquivo()), { recursive: true });
|
|
130
|
+
fs.writeFileSync(arquivo(), JSON.stringify(tudo, null, 2));
|
|
131
|
+
return { ok: true, chave: k, estilo: novo, avisos: v.avisos, comoUsar: `no próximo vídeo, roteiro.estiloVisual:"${k}" traz este estilo (ou {base:"${k}", ...} para misturar).` };
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/** O cardápio que a IA lê antes de montar o estilo. */
|
|
135
|
+
function cardapio() {
|
|
136
|
+
return {
|
|
137
|
+
ok: true,
|
|
138
|
+
total: total(),
|
|
139
|
+
pecas: { ...pecas(), paleta: paletas() },
|
|
140
|
+
salvos: listar(),
|
|
141
|
+
comoUsar: 'monte o estilo pelo ASSUNTO e passe roteiro.estiloVisual {humor, paleta, display, texto, mono, genero, ambiente, acento, material} no estilo gerativo. '
|
|
142
|
+
+ 'Paleta: um nome da lista ou 5 cores suas [escura, assinatura, papel, ousada, ousada]. Misture: {base:"<salvo>", display:"..."} troca só uma peça. '
|
|
143
|
+
+ 'Peça vazia, o PrimoCode completa sem repetir os últimos vídeos. Ficou bom? Pergunte se pode salvar e use acao:"salvar_estilo".',
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
module.exports = { pecas, paletas, total, validar, montar, salvar, ler, listar, cardapio, arquivo };
|
package/lib/estudio/gerativo.js
CHANGED
|
@@ -192,6 +192,15 @@ const HUMORES = {
|
|
|
192
192
|
paletas: [['#2A0E12', '#F0E6D6', '#C9A24A'], ['#F1EBE1', '#1B1A17', '#8C1C13']],
|
|
193
193
|
generos: ['jazz', 'soul-rnb', 'classical'], som: 'papel',
|
|
194
194
|
},
|
|
195
|
+
/* (14/10) A padaria caía no editorial — música clássica e mono de jornal —
|
|
196
|
+
porque nenhum humor era de comida. */
|
|
197
|
+
comida: {
|
|
198
|
+
palavras: 'pao paes padaria padeiro cafe cafeteria bolo bolos doce doces confeitaria forno fornada fermentacao massa farinha receita cozinha comida restaurante pizza lanche almoco jantar sabor chef croissant',
|
|
199
|
+
displays: ['Fraunces', 'Playfair Display', 'Bricolage Grotesque'], textos: ['Hanken Grotesk', 'Familjen Grotesk'], monos: ['Space Mono', 'Martian Mono'],
|
|
200
|
+
serifas: ['Instrument Serif', 'Fraunces'], cineticas: ['Anybody', 'Archivo'],
|
|
201
|
+
paletas: [['#F3E6CF', '#3B2416', '#B5651D'], ['#EFE3D0', '#2A1A12', '#C8553D']],
|
|
202
|
+
generos: ['acoustic', 'jazz', 'soul-rnb'], som: 'organico',
|
|
203
|
+
},
|
|
195
204
|
};
|
|
196
205
|
/* A cor que o próprio assunto diz ("por que o céu é AZUL") vira o acento. */
|
|
197
206
|
const CORES_DITAS = { azul: '#2546D9', vermelho: '#C8291E', verde: '#2F7D4F', laranja: '#FF5F1F', amarelo: '#E8B92F', dourado: '#C9A24A', rosa: '#E86A8A', coral: '#FF6F59' };
|
|
@@ -208,11 +217,15 @@ const ICONE_DE = {
|
|
|
208
217
|
notebook: 'celular', pronto: 'check', feito: 'check', certo: 'check', proximo: 'seta', passo: 'seta', adiante: 'seta', ricocheteia: 'seta',
|
|
209
218
|
};
|
|
210
219
|
|
|
220
|
+
/* O humor novo (comida, 14/10) só entra quando o roteiro diz as palavras dele:
|
|
221
|
+
não gasta a semente (a mesma semente segue dando o mesmo vídeo nos projetos
|
|
222
|
+
que já existem) nem tira a vez dos outros num roteiro que não é de comida. */
|
|
223
|
+
const SO_COM_PALAVRA = new Set(['comida']);
|
|
211
224
|
/** O humor do roteiro: as palavras dele contra as de cada humor (empate: a semente). */
|
|
212
225
|
function humorDe(roteiro, s) {
|
|
213
226
|
const n = acertosDeHumor(roteiro);
|
|
214
227
|
const nota = {};
|
|
215
|
-
for (const h of Object.keys(HUMORES)) nota[h] = n[h] + s.prox() * 0.5;
|
|
228
|
+
for (const h of Object.keys(HUMORES)) nota[h] = SO_COM_PALAVRA.has(h) ? (n[h] ? n[h] + 0.25 : -1) : n[h] + s.prox() * 0.5;
|
|
216
229
|
return Object.entries(nota).sort((a, b) => b[1] - a[1])[0][0];
|
|
217
230
|
}
|
|
218
231
|
/** Quantas palavras de cada humor o roteiro diz. */
|
|
@@ -241,7 +254,7 @@ function escolherVisual(roteiro = {}, { aleatorio = Math.random } = {}) {
|
|
|
241
254
|
const marca = require('./marca.js').marcaEmObjeto(roteiro.marca);
|
|
242
255
|
const n = acertosDeHumor(roteiro);
|
|
243
256
|
const max = Math.max(...Object.values(n));
|
|
244
|
-
const humor = variedade.sortear(Object.keys(HUMORES), 'humor-gerativo', { aleatorio, adequacao: (h) => (n[h] + 1) / (max + 1) });
|
|
257
|
+
const humor = variedade.sortear(Object.keys(HUMORES).filter((h) => !SO_COM_PALAVRA.has(h) || n[h]), 'humor-gerativo', { aleatorio, adequacao: (h) => (n[h] + 1) / (max + 1) });
|
|
245
258
|
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; };
|
|
246
259
|
const escolher = (m, tipo, prefixo) => variedade.sortear(Object.keys(m), tipo, { aleatorio, prefixo, adequacao: m });
|
|
247
260
|
const out = { humor };
|
|
@@ -323,7 +336,7 @@ const MOLDURAS = ['sem', 'cantos', 'completa'];
|
|
|
323
336
|
'bokeh' (as manchas de luz desfocadas na frente). O acento: 'traco' (cada
|
|
324
337
|
traço se desenha e some) ou 'viagem' (o ponto que nasce no meio, viaja
|
|
325
338
|
entre as batidas e se recolhe — o de antes). */
|
|
326
|
-
const AMBIENTES = ['limpo', 'luz', 'bokeh'];
|
|
339
|
+
const AMBIENTES = ['limpo', 'luz', 'bokeh', 'brilho'];
|
|
327
340
|
const ACENTOS_JEITO = ['traco', 'viagem'];
|
|
328
341
|
const RITMOS_ICONE = ['seguidos', 'alternados', 'esparsos'];
|
|
329
342
|
const MATERIAIS_ICONE = ['argila', 'verniz', 'metal', 'perola', 'acetinado'];
|
|
@@ -524,7 +537,8 @@ function pesosDe(c, { curto, palavras, primeira, ultima, tem3d, pergunta, exclam
|
|
|
524
537
|
case 'contraste': return { divisao: 5, duelo: 2.6 };
|
|
525
538
|
case 'citacao': return { citacao: 5, pilha: 1 };
|
|
526
539
|
case 'marca': return { marca: primeira || ultima ? 4.5 : 3, gigante: 2.4, parede: curto ? 2 : 0 };
|
|
527
|
-
|
|
540
|
+
// A marca é UMA linha: "Todo dia às 7h – Padaria Pão Quente" saía em 40 px (14/10). Chamada longa empilha.
|
|
541
|
+
case 'cta': return { pilha: 3, gigante: curto ? 2.6 : 0.6, parede: c.titulo.length <= 16 ? 1.6 : 0, marca: c.titulo.length <= 18 ? 1 : 0, produto: tem3d ? 3.5 : 0 };
|
|
528
542
|
case 'produto': return tem3d ? { produto: 9, pilha: 0.5 } : { pilha: 3, gigante: curto ? 2.5 : 0.5, parede: c.titulo.length <= 12 ? 1.2 : 0 };
|
|
529
543
|
case 'falado': return { palavras: 4, gigante: palavras <= 3 ? 2.6 : 0, pilha: palavras <= 9 ? 1.2 : 0 };
|
|
530
544
|
default: return {
|
|
@@ -628,6 +642,7 @@ const CAMPOS = {
|
|
|
628
642
|
energia: [['#0B0B0B', '#FF4F00', '#F2EEE6', '#1D3BD1', '#C9F23A'], ['#101010', '#E8C547', '#F4EBDD', '#E5322D', '#2A57FF']],
|
|
629
643
|
natureza: [['#0F2A2A', '#F2A541', '#F2EDE2', '#2546D9', '#E4573D'], ['#13233F', '#2546D9', '#F2EDE2', '#F2A541', '#C8553D']],
|
|
630
644
|
luxo: [['#120D0B', '#C9A24A', '#F0E6D6', '#5B1A1F', '#2F4F4F']],
|
|
645
|
+
comida: [['#2A1A12', '#C47A2C', '#F3E6CF', '#7A2E1C', '#E8C27A'], ['#1F1611', '#B5651D', '#F5ECDD', '#5E7D3A', '#E9B44C']],
|
|
631
646
|
};
|
|
632
647
|
function camposDe(marca, humor, s, dita = null, fixa = null) {
|
|
633
648
|
const c = marca.cores || {};
|
|
@@ -815,7 +830,25 @@ function desenharSom(P, { trilha = null, sons = {} } = {}) {
|
|
|
815
830
|
const eco = () => `aecho=0.8:${f(sh.entre(0.4, 0.75))}:${Math.round(sh.entre(30, 90))}|${Math.round(sh.entre(100, 220))}:${f(sh.entre(0.15, 0.35))}|${f(sh.entre(0.08, 0.2))}`;
|
|
816
831
|
const ss = () => f(sh.entre(4, Math.max(6, DUR + 20))); // de onde da trilha sai o pedaço
|
|
817
832
|
const sint = sonsDoVideo({ partes: P.batidas.map((b) => ({ titulo: b.comp, fala: String(b.de) })) }).receitas;
|
|
833
|
+
/* OS SONS PEQUENOS (13/10: "90% do motion é efeito sonoro: barulhinho
|
|
834
|
+
bonitinho, gostoso, satisfatório"). Sintetizados, limpos e cada um com
|
|
835
|
+
a sua altura: o tique da palavra que entra, o pop do ícone e do item, o
|
|
836
|
+
brilho da marca. Não vêm da trilha: são o "clique" do motion. */
|
|
837
|
+
const MICRO = new Set(['tique', 'pop', 'brilho']);
|
|
838
|
+
const receitaMicro = (gesto) => {
|
|
839
|
+
if (gesto === 'tique') {
|
|
840
|
+
const fq = Math.round(sh.entre(1800, 3200)), d = f(sh.entre(0.05, 0.08));
|
|
841
|
+
return ['-f', 'lavfi', '-i', `aevalsrc='0.55*sin(2*PI*${fq}*t)*exp(-${Math.round(sh.entre(70, 110))}*t)+0.12*(random(0)*2-1)*exp(-200*t)':s=44100:d=${d}`, '-af', `highpass=f=900,${panF()}`];
|
|
842
|
+
}
|
|
843
|
+
if (gesto === 'pop') {
|
|
844
|
+
const a0 = Math.round(sh.entre(700, 1200)), a1 = Math.round(sh.entre(380, 640));
|
|
845
|
+
return ['-f', 'lavfi', '-i', `aevalsrc='0.8*sin(2*PI*(${a0}-${a0 - a1}*t*5)*t)*exp(-${Math.round(sh.entre(22, 34))}*t)*(1-exp(-800*t))':s=44100:d=0.2`, '-af', panF()];
|
|
846
|
+
}
|
|
847
|
+
const b0 = Math.round(sh.entre(780, 1050));
|
|
848
|
+
return ['-f', 'lavfi', '-i', `aevalsrc='0.3*sin(2*PI*${b0}*t)*exp(-3*t)+0.2*sin(2*PI*${Math.round(b0 * 1.5)}*t)*exp(-3.8*t)+0.12*sin(2*PI*${b0 * 2}*t)*exp(-4.6*t)':s=44100:d=1.3`, '-af', `${eco()},${panF()}`];
|
|
849
|
+
};
|
|
818
850
|
const receita = (gesto) => {
|
|
851
|
+
if (MICRO.has(gesto)) return receitaMicro(gesto);
|
|
819
852
|
if (!musica) { // sem trilha: a síntese, um arquivo por evento (a semente muda a cada um)
|
|
820
853
|
const base = { revela: 'revela0', golpe: sh.um(['golpe0', 'golpe1']), fatia: sh.um(['nasce0', 'nasce1', 'nasce2']), gaguejo: sh.um(['passagem0', 'passagem1']), ar: sh.um(['passagem1', 'passagem2']) }[gesto];
|
|
821
854
|
const args = [...sint[base]];
|
|
@@ -854,7 +887,9 @@ function desenharSom(P, { trilha = null, sons = {} } = {}) {
|
|
|
854
887
|
};
|
|
855
888
|
const evento = (t, gesto, volume) => {
|
|
856
889
|
if (t < 0 || t > DUR - 0.1) return;
|
|
857
|
-
|
|
890
|
+
// Som grande não encosta em som grande (0,25 s); o pequeno só não encosta em nada a menos de 0,09 s.
|
|
891
|
+
const micro = MICRO.has(gesto);
|
|
892
|
+
if (efeitos.some((e) => Math.abs(e.t - t) < (micro || MICRO.has(e.gesto) ? 0.09 : 0.25))) return;
|
|
858
893
|
const nome = 'e' + String(efeitos.length).padStart(2, '0') + '-' + gesto;
|
|
859
894
|
receitas[nome] = receita(gesto);
|
|
860
895
|
efeitos.push({ t: f(Math.max(0, t + (sh.prox() - 0.5) * 0.02)), nome, gesto, volume: f(volume * (0.85 + sh.prox() * 0.3)) });
|
|
@@ -882,6 +917,18 @@ function desenharSom(P, { trilha = null, sons = {} } = {}) {
|
|
|
882
917
|
if (tr === 'atravessa' || tr === 'inunda') return evento(b.de - 0.4, 'ar', 0.75);
|
|
883
918
|
return evento(b.de - 0.08, 'fatia', 0.75);
|
|
884
919
|
});
|
|
920
|
+
// O estilo da casa ganha os sons pequenos: cada palavra da tese, cada ícone e item, e o brilho da marca.
|
|
921
|
+
if (P.somDenso) {
|
|
922
|
+
bs.forEach((b) => {
|
|
923
|
+
for (const l of b.linhas || []) {
|
|
924
|
+
if (l.papel === 'tese' || l.papel === 'numero') for (const p of l.palavras || []) if (p.t != null) evento(p.t, 'tique', 0.32);
|
|
925
|
+
if (l.papel === 'item' && l.t != null) evento(l.t, 'pop', 0.42);
|
|
926
|
+
}
|
|
927
|
+
if (b.icone3d && b.icone3d.t != null) evento(b.icone3d.t, 'pop', 0.5);
|
|
928
|
+
if (b.comp === 'marca') evento(b.de + 0.35, 'brilho', 0.55);
|
|
929
|
+
if (b.livre) evento(b.de + 0.08, 'pop', 0.45); // a cena da IA entra com um pop
|
|
930
|
+
});
|
|
931
|
+
}
|
|
885
932
|
efeitos.sort((a, b) => a.t - b.t);
|
|
886
933
|
// O src é o nome que sons-leves.prepararReceitas dá (semente + nome): um arquivo por evento.
|
|
887
934
|
const sufixo = (P.semente >>> 0).toString(36);
|
|
@@ -905,6 +952,27 @@ function montarGerativo({ roteiro, largura = 1920, altura = 1080, tempos = [], n
|
|
|
905
952
|
const partitura = partituraDe({ roteiro: { ...roteiro, partes }, largura, altura, tempos, icones });
|
|
906
953
|
const DUR = partitura.duracao;
|
|
907
954
|
|
|
955
|
+
/* A PARTE QUE A IA DESENHA (13/10: "a IA vai criar os estilos, os ícones,
|
|
956
|
+
as coisas"). A parte com `componente` aponta um React escrito pela IA na
|
|
957
|
+
pasta do projeto; ele ocupa o quadro no tempo da batida e recebe em
|
|
958
|
+
el.dados o texto, cada palavra com o seu tempo (t relativo ao início da
|
|
959
|
+
batida), as cores e as fontes. O Estúdio segue cuidando da voz, da
|
|
960
|
+
trilha, dos sons e do fundo; a batida deixa de desenhar o texto dela. */
|
|
961
|
+
const livres = [];
|
|
962
|
+
partitura.batidas.forEach((b) => {
|
|
963
|
+
const pt = partes[b.parte];
|
|
964
|
+
const src = pt && typeof pt.componente === 'string' ? pt.componente.trim() : '';
|
|
965
|
+
if (!src) return;
|
|
966
|
+
const palavras = (b.linhas || []).flatMap((l) => (l.palavras || []).map((p) => ({ texto: p.texto, t: p.t != null ? r3(p.t - b.de) : null })));
|
|
967
|
+
livres.push({ tipo: 'componente', src, de: b.de, ate: b.ate, dados: {
|
|
968
|
+
fala: pt.fala || '', titulo: pt.titulo || '', palavras, duracao: r3(b.ate - b.de),
|
|
969
|
+
cores: b.cores, fontes: partitura.fontes, largura, altura, ...(pt.dados && typeof pt.dados === 'object' ? pt.dados : {}) } });
|
|
970
|
+
// As linhas ficam (o leiaute do kit conta com elas), mas ocultas: quem desenha é a cena da IA.
|
|
971
|
+
for (const l of b.linhas || []) l.oculta = true;
|
|
972
|
+
b.acento = []; b.livre = true;
|
|
973
|
+
delete b.heroi; delete b.icone3d; delete b.textura; delete b.contador;
|
|
974
|
+
});
|
|
975
|
+
|
|
908
976
|
// ── o som: efeitos nas transformações, a música abaixa sob a voz ─────
|
|
909
977
|
/* O SOM (desenharSom): um evento por batida, no máximo — e cada um é um
|
|
910
978
|
arquivo PRÓPRIO, feito da trilha do vídeo (ou sintetizado, sem trilha). */
|
|
@@ -928,7 +996,7 @@ function montarGerativo({ roteiro, largura = 1920, altura = 1080, tempos = [], n
|
|
|
928
996
|
// As chaves "fonte*" são o que o fontes.css procura (remotion.js).
|
|
929
997
|
fonte: partitura.fontes.display, fonteTexto: partitura.fontes.texto, fonteMono: partitura.fontes.mono,
|
|
930
998
|
fonteCinetica: partitura.fontes.cinetica, fonteSerifa: partitura.fontes.serifa,
|
|
931
|
-
}],
|
|
999
|
+
}, ...livres],
|
|
932
1000
|
}],
|
|
933
1001
|
audio: {
|
|
934
1002
|
...(narracao ? { narracao } : {}),
|
|
@@ -1498,6 +1566,34 @@ function partituraDe({ roteiro, largura = 1920, altura = 1080, tempos = [], icon
|
|
|
1498
1566
|
}
|
|
1499
1567
|
});
|
|
1500
1568
|
|
|
1569
|
+
/* O ESTILO DA CASA (13/10, as referências do Joel — o "Vantage"): o vídeo
|
|
1570
|
+
inteiro no chão escuro da marca, com o brilho do acento derivando no
|
|
1571
|
+
fundo; a frase média (título 7–11% da altura, motionmaxxing SKILL:134),
|
|
1572
|
+
não a palavra gigante; cada palavra entra inteira, desfocando (type.md
|
|
1573
|
+
R2.6), na hora em que a voz a diz; sem a parede de texto repetido. */
|
|
1574
|
+
if (roteiro.casa) {
|
|
1575
|
+
fundo.ambiente = 'brilho';
|
|
1576
|
+
const escura = batidas.reduce((m, b) => (b.cores && (!m || luz(b.cores.fundo) < luz(m.fundo)) ? b.cores : m), null);
|
|
1577
|
+
const teto = (deitado ? 0.11 : 0.075) * H;
|
|
1578
|
+
for (const b of batidas) {
|
|
1579
|
+
if (escura) b.cores = { ...escura };
|
|
1580
|
+
b.troca = 'corte';
|
|
1581
|
+
// A saída é da LINHA inteira (desliza) ou corte seco: o "evapora" e o "comprime" espalhavam as letras ("a ad e").
|
|
1582
|
+
if (b.saida && b.saida !== 'fim') b.saida = batidas.indexOf(b) % 2 ? 'corte' : 'desliza';
|
|
1583
|
+
delete b.textura;
|
|
1584
|
+
// O enfeite abstrato (colunas 3D, truchet, a textura de curvas) sai; o ícone do site (b.icone3d) fica.
|
|
1585
|
+
delete b.heroi;
|
|
1586
|
+
for (const l of b.linhas || []) {
|
|
1587
|
+
// O teto vale também para o "encher" do kit (cresce até l.max): sem isso a tese gigante passava de 30% da altura.
|
|
1588
|
+
if ((l.papel === 'tese' || l.papel === 'numero' || l.papel === 'fraco') && !l.marca) {
|
|
1589
|
+
if (l.tamanho > teto) l.tamanho = Math.round(teto);
|
|
1590
|
+
if (l.encher) l.max = Math.round(Math.min(l.max || teto, teto));
|
|
1591
|
+
}
|
|
1592
|
+
if (l.papel === 'tese' && !(b.contador && b.contador.linha === l.id)) l.comportamento = 'desfoque';
|
|
1593
|
+
}
|
|
1594
|
+
}
|
|
1595
|
+
}
|
|
1596
|
+
|
|
1501
1597
|
return {
|
|
1502
1598
|
versao: 1, semente, fps: FPS, humor,
|
|
1503
1599
|
musica: { genero: ou(E.genero, s.um(H0.generos)), generos: H0.generos }, largura: W, altura: H, duracao, regras: REGRAS,
|
|
@@ -1505,6 +1601,7 @@ function partituraDe({ roteiro, largura = 1920, altura = 1080, tempos = [], icon
|
|
|
1505
1601
|
margem: M, fundo, batidas, camera, produtos,
|
|
1506
1602
|
identidade: { chaos: ident.chaos, acento: ident.acento, comeca: campos[comeca], fontes: [display, texto] }, secoes: secoes.nomes,
|
|
1507
1603
|
hud: molduraDe(roteiro, E, { W, H, deitado, marca, secoes: secoes.nomes }),
|
|
1604
|
+
somDenso: !!roteiro.casa,
|
|
1508
1605
|
};
|
|
1509
1606
|
}
|
|
1510
1607
|
|
|
@@ -1942,4 +2039,5 @@ function contarAcentos(b) {
|
|
|
1942
2039
|
module.exports = { tituloDaFala,
|
|
1943
2040
|
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,
|
|
1944
2041
|
especEixos, sorteador, hash, larguraEm, conteudoDe, casar, HEROIS_OBJETO, HEROIS_CAMPO, HEROIS_3D, FAMILIA_HEROI,
|
|
2042
|
+
CAMPOS, escolhasValidas,
|
|
1945
2043
|
};
|