primocode 10.0.9 → 10.2.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 +4 -4
- package/bin/primocode.js +25 -28
- package/lib/act.js +5 -2
- package/lib/catalogo.js +7 -4
- package/lib/config.js +10 -2
- package/lib/estudio/assets.js +86 -6
- package/lib/estudio/batida.js +184 -0
- package/lib/estudio/biblioteca.js +214 -0
- package/lib/estudio/cinema.js +37 -17
- package/lib/estudio/composicao.js +2 -1
- package/lib/estudio/decisao.js +106 -0
- package/lib/estudio/edicao.js +36 -17
- package/lib/estudio/estilo.js +35 -8
- package/lib/estudio/fluido.js +352 -0
- package/lib/estudio/gerativo.js +21 -3
- package/lib/estudio/hibrido.js +16 -12
- package/lib/estudio/impacto.js +66 -31
- package/lib/estudio/index.js +245 -42
- package/lib/estudio/kit/fluido.jsx +614 -0
- package/lib/estudio/kit/gerativo.jsx +15 -5
- package/lib/estudio/kit/index.jsx +2 -1
- package/lib/estudio/kit/tres.jsx +88 -21
- package/lib/estudio/kit.js +3 -1
- package/lib/estudio/kokoro.js +111 -0
- package/lib/estudio/lancamento.js +3 -4
- package/lib/estudio/legibilidade.js +20 -0
- package/lib/estudio/referencias.js +36 -25
- package/lib/estudio/remotion.js +53 -6
- package/lib/estudio/revisor.js +117 -0
- package/lib/estudio/variacao3d.js +46 -0
- package/lib/estudio/variedade.js +140 -8
- package/lib/portao-skill.js +150 -0
- package/lib/skills.js +15 -1
- package/lib/tools.js +31 -5
- package/lib/video/filters.js +6 -1
- package/lib/video/ops.js +4 -3
- package/lib/video/timeline.js +1 -1
- package/lib/video/zoom.js +275 -0
- package/package.json +2 -2
- package/skills/escrever-em-portugues/SKILL.md +3 -0
- package/skills/interface-revisada/SKILL.md +7 -1
- package/skills/motion-de-primeira/SKILL.md +3 -5
- package/skills/projeto-no-github/SKILL.md +6 -0
- package/skills/publicar-na-internet/SKILL.md +3 -0
- package/skills/site-com-motion/SKILL.md +2 -0
- package/studio/voz_kokoro.py +98 -0
package/lib/estudio/impacto.js
CHANGED
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
|
|
25
25
|
const { caber, paleta } = require('./composicao.js');
|
|
26
26
|
const { iconeDe, peso } = require('./edicao.js');
|
|
27
|
+
const { minimoLeitura, sobra } = require('./legibilidade.js');
|
|
27
28
|
|
|
28
29
|
const r2 = (n) => Math.round(n * 100) / 100;
|
|
29
30
|
const limpa = (t) => String(t || '').replace(/^[^\p{L}\p{N}]+|[^\p{L}\p{N}%!?]+$/gu, '');
|
|
@@ -72,11 +73,12 @@ function montarImpacto({ roteiro, largura = 1080, altura = 1920, tempos = [], na
|
|
|
72
73
|
const icOk = (ic) => ic && (!iconesOk || iconesOk.has(ic));
|
|
73
74
|
|
|
74
75
|
// A palavra que bate: letra gigante, a palavra forte na cor de destaque.
|
|
75
|
-
const bater = (els, texto, { y = null, corTexto = cor.tinta, destaque = cor.destaque, max = (deitado ? H * 0.2 : W * 0.2), linhas = 3, glitch = false, girar = 0 } = {}) => {
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
25/09: "CONSTRUÇÃO" e
|
|
76
|
+
const bater = (els, texto, { y = null, corTexto = cor.tinta, destaque = cor.destaque, max = (deitado ? H * 0.2 : W * 0.2), linhas = 3, glitch = false, girar = 0, entra = 0.22 } = {}) => {
|
|
77
|
+
// Como a pessoa escreveu: caixa alta forçada era a "cara de IA" (05/10).
|
|
78
|
+
const t = String(texto || '');
|
|
79
|
+
/* Peso 900 é mais largo que a letra comum, e a batida ainda cresce 5%:
|
|
80
|
+
mede numa caixa mais estreita (medido em 25/09: "CONSTRUÇÃO" e
|
|
81
|
+
"DEMONSTRAÇÃO" saíam pela borda). */
|
|
80
82
|
const fit = caber(t, { largura: W0 * 0.8, linhas, max, fonte: F.titulo });
|
|
81
83
|
const h = fit.tamanho * 0.98 * fit.linhas;
|
|
82
84
|
const ws = t.split(/\s+/);
|
|
@@ -85,12 +87,14 @@ function montarImpacto({ roteiro, largura = 1080, altura = 1920, tempos = [], na
|
|
|
85
87
|
els.push({
|
|
86
88
|
tipo: 'texto', texto: t, fonte: F.titulo, peso: 900, tamanho: fit.tamanho, cor: corTexto, entrelinha: 0.98,
|
|
87
89
|
x: M, y: Math.round(y == null ? (H - h) / 2 : y), largura: W0, alinhamento: 'center', espacamento: -1,
|
|
88
|
-
|
|
90
|
+
// A palavra forte é UM acento: a serifa em itálico, que chega um instante depois.
|
|
91
|
+
...(forte >= 0 && ws.length > 1 ? { revelar: ws.map(() => 0), destaques: [forte], corDestaque: destaque, fonteDestaque: 'Instrument Serif' } : {}),
|
|
89
92
|
...(glitch ? { glitch: { forca: 14, dur: 0.3 } } : {}),
|
|
90
93
|
anima: {
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
+
// A entrada encolhe junto (entra < 0,22) quando a batida é curta: a leitura não.
|
|
95
|
+
escala: [{ t: 0, v: 1.45 }, { t: r2(entra * 0.6), v: 0.97, e: 'saida' }, { t: r2(entra), v: 1, e: 'suave' }, { t: 3, v: 1.05, e: 'linear' }],
|
|
96
|
+
opacidade: [{ t: 0, v: 0 }, { t: r2(entra * 0.23), v: 1 }],
|
|
97
|
+
rotacao: [{ t: 0, v: girar * 2 }, { t: r2(entra * 0.9), v: girar, e: 'saida' }],
|
|
94
98
|
},
|
|
95
99
|
});
|
|
96
100
|
return h;
|
|
@@ -126,18 +130,51 @@ function montarImpacto({ roteiro, largura = 1080, altura = 1920, tempos = [], na
|
|
|
126
130
|
let bs = batidas((tp.palavras || []).filter((w) => w && w.texto), fim);
|
|
127
131
|
if (!bs.length) bs = [[{ de: ini, ate: fim, texto: p.titulo || p.sim || p.valor || '' }]];
|
|
128
132
|
const itens = (p.itens || []).map((it) => (typeof it === 'string' ? it : it.texto));
|
|
133
|
+
const semNaoE = (t) => String(t || '').replace(/^\s*(n[aã]o\s+[ée]\s+|[ée]\s+)/i, '');
|
|
134
|
+
// Os textos que a batida k (de n) mostra — os mesmos que o desenho abaixo usa.
|
|
135
|
+
const textosDe = (b, k, n) => {
|
|
136
|
+
const falado = b.map((w) => limpa(w.texto)).join(' ');
|
|
137
|
+
if (p.tipo === 'numero' && k === 0) return [String(p.valor), p.rotulo || ''];
|
|
138
|
+
if ((p.tipo === 'lista' || p.tipo === 'passos') && itens.length) {
|
|
139
|
+
const it = itens[Math.min(k, itens.length - 1)];
|
|
140
|
+
return [p.tipo === 'passos' ? `${k + 1}. ${it}` : it, k === 0 ? p.titulo || '' : ''];
|
|
141
|
+
}
|
|
142
|
+
if (p.tipo === 'cta' && k === n - 1) return [p.titulo || falado, p.link || ''];
|
|
143
|
+
if (p.tipo === 'contraste') return [k < n / 2 ? `não é ${semNaoE(p.nao)}` : semNaoE(p.sim)];
|
|
144
|
+
// O título no fim só se der tempo de ler; senão, a fala dessa batida.
|
|
145
|
+
return [k === n - 1 && p.titulo && k > 0 && vao(k) >= minimoLeitura(p.titulo) + 0.08 ? p.titulo : falado];
|
|
146
|
+
};
|
|
147
|
+
/* A lei do /brag: cada batida fica no ar a entrada mínima mais
|
|
148
|
+
max(0,8 s; 0,3 s por palavra) — dentro do tempo da parte, sem esticar
|
|
149
|
+
o vídeo. Batida curta pega a folga da vizinha (o corte anda um pouco);
|
|
150
|
+
sem folga, gruda nela. */
|
|
151
|
+
const inis = bs.map((b, k) => (k === 0 ? ini : b[0].de - 0.04));
|
|
152
|
+
const vao = (k) => (k < bs.length - 1 ? inis[k + 1] : fim) - inis[k];
|
|
153
|
+
const precisa = (k) => Math.max(...textosDe(bs[k], k, bs.length).filter(Boolean).map(minimoLeitura))
|
|
154
|
+
+ (p.tipo === 'cta' && k === bs.length - 1 && p.link ? 0.23 : 0) + 0.08; // o link entra depois do título
|
|
155
|
+
for (let k = 0; bs.length > 1 && k < bs.length;) {
|
|
156
|
+
const falta = precisa(k) - vao(k);
|
|
157
|
+
if (falta <= 0) { k++; continue; }
|
|
158
|
+
if (k + 1 < bs.length && vao(k + 1) - falta >= precisa(k + 1)) { inis[k + 1] = r2(inis[k + 1] + falta); k++; continue; }
|
|
159
|
+
if (k > 0 && vao(k - 1) - falta >= precisa(k - 1)) { inis[k] = r2(inis[k] - falta); k++; continue; }
|
|
160
|
+
const a = k > 0 ? k - 1 : 0;
|
|
161
|
+
bs.splice(a, 2, [...bs[a], ...bs[a + 1]]);
|
|
162
|
+
inis.splice(a + 1, 1);
|
|
163
|
+
k = 0;
|
|
164
|
+
}
|
|
129
165
|
|
|
130
166
|
// Impacto no começo de cada parte; o riser anuncia a chamada final.
|
|
131
167
|
som(ini, 'impacto', i === 0 ? 0.9 : 0.6);
|
|
132
168
|
if (p.tipo === 'cta') som(ini - 2, 'riser', 0.6);
|
|
133
169
|
|
|
134
170
|
bs.forEach((b, k) => {
|
|
135
|
-
const de = k
|
|
136
|
-
const ate = k < bs.length - 1 ?
|
|
171
|
+
const de = inis[k];
|
|
172
|
+
const ate = k < bs.length - 1 ? inis[k + 1] : fim;
|
|
137
173
|
const falado = b.map((w) => limpa(w.texto)).join(' ');
|
|
138
174
|
const els = [];
|
|
139
175
|
const cena = { fundo: { tipo: 'cor', valor: cor.fundo }, elementos: els };
|
|
140
176
|
const ultimo = k === bs.length - 1;
|
|
177
|
+
const entra = r2(sobra(vao(k), precisa(k) - 0.08, 0.08, 0.22));
|
|
141
178
|
let tela; // 'palavra' | 'foto' | 'marca'
|
|
142
179
|
|
|
143
180
|
if (p.tipo === 'numero' && k === 0) {
|
|
@@ -148,8 +185,8 @@ function montarImpacto({ roteiro, largura = 1080, altura = 1920, tempos = [], na
|
|
|
148
185
|
const fit = caber(valor, { largura: W0, linhas: 1, max: H * (deitado ? 0.55 : 0.32), fonte: F.titulo });
|
|
149
186
|
els.push({ tipo: 'texto', texto: valor, fonte: F.titulo, peso: 900, tamanho: fit.tamanho, cor: '#FFFFFF', x: M, y: Math.round(H * 0.5 - fit.tamanho * 0.62), largura: W0, alinhamento: 'center',
|
|
150
187
|
...(m ? { contar: { ate: Number(m[2].replace(/\./g, '').replace(',', '.')), dur: 0.7, antes: m[1], depois: m[3] } } : {}),
|
|
151
|
-
anima: { escala: [{ t: 0, v: 1.6 }, { t: 0.18, v: 1, e: 'saida' }] } });
|
|
152
|
-
bater(els, p.rotulo || '', { y: H * 0.5 + fit.tamanho * 0.45, max: H * 0.07, linhas: 2, corTexto: '#FFFFFF', destaque: '#FFFFFF' });
|
|
188
|
+
anima: { escala: [{ t: 0, v: 1.6 }, { t: r2(Math.min(0.18, entra)), v: 1, e: 'saida' }] } });
|
|
189
|
+
bater(els, p.rotulo || '', { y: H * 0.5 + fit.tamanho * 0.45, max: H * 0.07, linhas: 2, corTexto: '#FFFFFF', destaque: '#FFFFFF', entra });
|
|
153
190
|
cena.tremor = [{ de: 0, ate: 0.5, forca: 26 }];
|
|
154
191
|
tela = 'marca';
|
|
155
192
|
} else if ((p.tipo === 'lista' || p.tipo === 'passos') && itens.length) {
|
|
@@ -159,9 +196,9 @@ function montarImpacto({ roteiro, largura = 1080, altura = 1920, tempos = [], na
|
|
|
159
196
|
const rot = p.tipo === 'passos' ? `${k + 1}. ${it}` : it;
|
|
160
197
|
const temE = emoji(els, it, { y: H * (deitado ? 0.28 : 0.3) });
|
|
161
198
|
// No gradiente, o destaque é o escuro da marca: laranja sobre azul some.
|
|
162
|
-
bater(els, rot, { y: temE ? H * (deitado ? 0.48 : 0.45) : null, max: deitado ? H * 0.11 : W * 0.12, linhas: 4, glitch: k === 0,
|
|
199
|
+
bater(els, rot, { y: temE ? H * (deitado ? 0.48 : 0.45) : null, max: deitado ? H * 0.11 : W * 0.12, linhas: 4, glitch: k === 0, entra,
|
|
163
200
|
...(k % 2 === 1 ? { corTexto: '#FFFFFF', destaque: cor.fundo } : {}) });
|
|
164
|
-
if (k === 0 && p.titulo) els.push({ tipo: 'texto', texto: String(p.titulo)
|
|
201
|
+
if (k === 0 && p.titulo) els.push({ tipo: 'texto', texto: String(p.titulo), fonte: F.texto, peso: 700, tamanho: Math.round(Math.min(W, H) * 0.04), cor: cor.destaque, x: M, y: M * 2.2, largura: W0, alinhamento: 'center' });
|
|
165
202
|
tela = k % 2 ? 'marca' : 'palavra';
|
|
166
203
|
cena.socos = [{ t: 0, forca: 0.14 }];
|
|
167
204
|
som(de, 'estalo', 0.55);
|
|
@@ -173,7 +210,7 @@ function montarImpacto({ roteiro, largura = 1080, altura = 1920, tempos = [], na
|
|
|
173
210
|
els.push({ tipo: 'forma', forma: 'circulo', largura: R, altura: R, x: Math.round(W / 2 - R / 2), y: Math.round(H * 0.4 - R / 2), cor: grad[1], opacidade: 0.4,
|
|
174
211
|
anima: { desfoque: [{ t: 0, v: Math.round(R * 0.2) }], escala: [{ t: 0, v: 0.7 }, { t: 1, v: 1.1, e: 'suave' }] } });
|
|
175
212
|
const yT = H * 0.26;
|
|
176
|
-
const hT = bater(els, p.titulo || falado, { y: yT, max: deitado ? H * 0.14 : W * 0.14, linhas: 3, corTexto: '#FFFFFF', destaque: cor.destaque });
|
|
213
|
+
const hT = bater(els, p.titulo || falado, { y: yT, max: deitado ? H * 0.14 : W * 0.14, linhas: 3, corTexto: '#FFFFFF', destaque: cor.destaque, entra });
|
|
177
214
|
if (p.link) {
|
|
178
215
|
const fl = Math.round(Math.min(W, H) * 0.055);
|
|
179
216
|
const wP = Math.min(W0, String(p.link).length * fl * 0.62 + fl * 2.4);
|
|
@@ -193,39 +230,37 @@ function montarImpacto({ roteiro, largura = 1080, altura = 1920, tempos = [], na
|
|
|
193
230
|
// O rodízio das três telas, com a primeira batida sempre na palavra.
|
|
194
231
|
const ciclo = ['palavra', 'foto', 'marca', 'foto'];
|
|
195
232
|
tela = k === 0 ? (p.tipo === 'imagem' || i === 0 ? 'foto' : 'palavra') : ciclo[(nBatida + k) % ciclo.length];
|
|
196
|
-
const
|
|
197
|
-
const texto = p.tipo === 'contraste' ? (k < bs.length / 2 ? `não é ${semNaoE(p.nao)}` : semNaoE(p.sim))
|
|
198
|
-
: (ultimo && p.titulo && k > 0 ? p.titulo : falado);
|
|
233
|
+
const texto = textosDe(b, k, bs.length)[0];
|
|
199
234
|
if (tela === 'foto' && fotos.length) {
|
|
200
235
|
fotoCheia(els, proxFoto());
|
|
201
|
-
bater(els, texto, { y: H * (deitado ? 0.56 : 0.6), max: deitado ? H * 0.12 : W * 0.13, linhas: 3 });
|
|
236
|
+
bater(els, texto, { y: H * (deitado ? 0.56 : 0.6), max: deitado ? H * 0.12 : W * 0.13, linhas: 3, entra });
|
|
202
237
|
if (k % 2 === 0) cena.flashes = [0];
|
|
203
238
|
} else if (tela === 'marca') {
|
|
204
239
|
cena.fundo = { tipo: 'gradiente', cores: [grad[0], grad[1]], angulo: 135 };
|
|
205
240
|
const temE = emoji(els, texto, { y: H * (deitado ? 0.26 : 0.28) });
|
|
206
|
-
bater(els, texto, { y: temE ? H * (deitado ? 0.46 : 0.44) : null, corTexto: '#FFFFFF', destaque: cor.fundo, max: deitado ? H * 0.16 : W * 0.16 });
|
|
241
|
+
bater(els, texto, { y: temE ? H * (deitado ? 0.46 : 0.44) : null, corTexto: '#FFFFFF', destaque: cor.fundo, max: deitado ? H * 0.16 : W * 0.16, entra });
|
|
207
242
|
} else {
|
|
208
243
|
tela = 'palavra';
|
|
209
244
|
const temE = emoji(els, texto, { y: H * (deitado ? 0.25 : 0.27) });
|
|
210
245
|
bater(els, texto, { y: temE ? H * (deitado ? 0.45 : 0.43) : null, glitch: p.tipo === 'contraste' || k % 3 === 0,
|
|
211
|
-
girar: k % 2 ? -2 : 2, corTexto: p.tipo === 'contraste' && k < bs.length / 2 ? 'rgba(255,255,255,0.55)' : cor.tinta });
|
|
246
|
+
girar: k % 2 ? -2 : 2, entra, corTexto: p.tipo === 'contraste' && k < bs.length / 2 ? 'rgba(255,255,255,0.55)' : cor.tinta });
|
|
212
247
|
cena.socos = [{ t: 0, forca: 0.2 }];
|
|
213
248
|
}
|
|
214
249
|
if (k === 0) cena.tremor = [{ de: 0, ate: 0.35, forca: 18 }];
|
|
215
250
|
som(de, tela === 'foto' ? 'whoosh' : 'estalo', tela === 'foto' ? 0.5 : 0.5);
|
|
216
251
|
}
|
|
217
252
|
if (!(p.tipo === 'cta' && ultimo)) logoTopo(els);
|
|
218
|
-
/* A
|
|
219
|
-
letra pequena
|
|
253
|
+
/* A LINHA DE CIMA: o subtítulo da parte (é onde moram a data e o local)
|
|
254
|
+
em letra pequena no alto — o anúncio não pode esconder a data. Sem a
|
|
255
|
+
faixa de cor atrás (caixa de cor era a "cara de IA", 05/10): só o
|
|
256
|
+
texto, que sobe e aparece. */
|
|
220
257
|
if (p.sub && p.tipo !== 'numero' && !(p.tipo === 'lista' || p.tipo === 'passos')) {
|
|
221
|
-
const fk = Math.round(Math.min(W, H) * 0.
|
|
222
|
-
els.push({ tipo: 'forma', forma: 'retangulo', x: M, y: Math.round(H * (deitado ? 0.14 : 0.1)), largura: W0, altura: Math.round(fk * 2), cor: cor.destaque, raio: Math.round(fk),
|
|
223
|
-
anima: { escalaX: [{ t: 0.05, v: 0 }, { t: 0.3, v: 1, e: 'saida' }] }, origem: 'center' });
|
|
258
|
+
const fk = Math.round(Math.min(W, H) * 0.036);
|
|
224
259
|
// Com largura, e não pela âncora: sem largura o texto quebrava na metade da tela.
|
|
225
|
-
const tk = caber(String(p.sub)
|
|
226
|
-
els.push({ tipo: 'texto', texto: String(p.sub)
|
|
227
|
-
cor:
|
|
228
|
-
anima: { opacidade: [{ t: 0.2, v: 0 }, { t: 0.
|
|
260
|
+
const tk = caber(String(p.sub), { largura: W0 * 0.78, linhas: 1, max: fk, fonte: F.texto }).tamanho;
|
|
261
|
+
els.push({ tipo: 'texto', texto: String(p.sub), fonte: F.texto, peso: 600, tamanho: tk, entrelinha: 1,
|
|
262
|
+
cor: tela === 'marca' ? '#FFFFFF' : cor.destaque, x: M, largura: W0, alinhamento: 'center', y: Math.round(H * (deitado ? 0.14 : 0.1)),
|
|
263
|
+
anima: { opacidade: [{ t: 0.2, v: 0 }, { t: 0.5, v: 1 }], y: [{ t: 0.2, v: 12 }, { t: 0.6, v: 0, e: 'saida' }] } });
|
|
229
264
|
}
|
|
230
265
|
|
|
231
266
|
/* Uma transição curta a cada tanto; o resto é corte seco — é o
|