primocode 9.8.0-beta.10 → 9.8.0-beta.12
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/catalogo.js +4 -1
- package/lib/claude-engine.js +25 -3
- package/lib/estudio/edicao.js +401 -0
- package/lib/estudio/index.js +100 -1
- package/lib/estudio/remotion.js +67 -3
- package/lib/local/cerebro.json +334 -185
- package/lib/local/entrada.js +18 -8
- package/lib/local/motor-cli.js +1 -1
- package/package.json +2 -2
- package/studio/narrar.py +110 -10
package/lib/estudio/remotion.js
CHANGED
|
@@ -212,6 +212,7 @@ function Elemento({ el, t, dados }) {
|
|
|
212
212
|
transform: [base.transform, move].filter(Boolean).join(' '),
|
|
213
213
|
filter: filtroDe(a) || undefined,
|
|
214
214
|
mixBlendMode: el.mistura || undefined,
|
|
215
|
+
transformOrigin: el.origem || undefined,
|
|
215
216
|
};
|
|
216
217
|
|
|
217
218
|
if (el.vidro) {
|
|
@@ -294,7 +295,7 @@ function Elemento({ el, t, dados }) {
|
|
|
294
295
|
// O SVG foi baixado para a pasta do projeto (estudio/assets.js): render
|
|
295
296
|
// que busca ícone na internet falha no meio, depois de tudo pronto.
|
|
296
297
|
return (
|
|
297
|
-
<div style={{ ...estilo, width: medida(el.tamanho, '96px'), height: medida(el.tamanho, '96px'), color: el.cor || '#fff' }}
|
|
298
|
+
<div style={{ ...estilo, width: medida(el.tamanho, '96px'), height: medida(el.tamanho, '96px'), fontSize: medida(el.tamanho, '96px'), lineHeight: 0, color: el.cor || '#fff' }}
|
|
298
299
|
dangerouslySetInnerHTML={{ __html: dados.icones[el.nome] || '' }} />
|
|
299
300
|
);
|
|
300
301
|
|
|
@@ -302,6 +303,55 @@ function Elemento({ el, t, dados }) {
|
|
|
302
303
|
const fala = (el.fala || []).find((f) => t >= f.de && t <= f.ate);
|
|
303
304
|
if (!fala) return null;
|
|
304
305
|
const e = el.estilo || {};
|
|
306
|
+
/* LEGENDA DE VIDEOMAKER: com o tempo de cada palavra, elas entram uma a
|
|
307
|
+
uma, no instante em que são ditas, com um salto curto. A que está
|
|
308
|
+
sendo dita ganha a cor de destaque; a palavra forte (a ideia da
|
|
309
|
+
frase) ganha a cor dela e fica maior. Sem "palavras", a frase entra
|
|
310
|
+
inteira, como antes. */
|
|
311
|
+
if (Array.isArray(fala.palavras) && fala.palavras.length) {
|
|
312
|
+
const destaque = e.destaque || '#FFD400';
|
|
313
|
+
const forte = e.corForte || '#00E0A4';
|
|
314
|
+
const soUma = e.modo === 'palavra';
|
|
315
|
+
const visiveis = soUma
|
|
316
|
+
? fala.palavras.filter((p, i, l) => t >= p.de && (i === l.length - 1 || t < l[i + 1].de)).slice(-1)
|
|
317
|
+
: fala.palavras.filter((p) => t >= p.de - 0.02);
|
|
318
|
+
return (
|
|
319
|
+
<div style={{
|
|
320
|
+
...estilo,
|
|
321
|
+
display: 'flex', flexWrap: 'wrap', justifyContent: 'center', alignItems: 'baseline',
|
|
322
|
+
gap: '0 0.34em', maxWidth: medida(e.largura, '86%'),
|
|
323
|
+
fontFamily: e.fonte ? '"' + e.fonte + '", system-ui, sans-serif' : 'system-ui, sans-serif',
|
|
324
|
+
fontSize: medida(e.tamanho, '64px'),
|
|
325
|
+
fontWeight: e.peso || 900,
|
|
326
|
+
textTransform: e.caixa || undefined,
|
|
327
|
+
lineHeight: 1.1,
|
|
328
|
+
}}>
|
|
329
|
+
{visiveis.map((p, i) => {
|
|
330
|
+
const idade = t - p.de;
|
|
331
|
+
const entra = Math.min(1, Math.max(0, idade / 0.14));
|
|
332
|
+
const salto = entra < 1 ? 0.7 + 0.42 * CURVAS.saida(entra) : 1.12 - 0.12 * Math.min(1, (idade - 0.14) / 0.12);
|
|
333
|
+
const falando = t >= p.de && t <= p.ate + 0.05;
|
|
334
|
+
const cor = p.forte ? forte : (falando ? destaque : (e.cor || '#fff'));
|
|
335
|
+
return (
|
|
336
|
+
<span key={i} style={{
|
|
337
|
+
display: 'inline-block',
|
|
338
|
+
// A palavra forte é MAIOR na fonte, não na escala: escala não
|
|
339
|
+
// abre espaço, e ela cresceria por cima da vizinha.
|
|
340
|
+
fontSize: p.forte ? '1.22em' : undefined,
|
|
341
|
+
transform: 'scale(' + salto + ') translateY(' + (1 - entra) * 18 + 'px)',
|
|
342
|
+
opacity: entra,
|
|
343
|
+
color: cor,
|
|
344
|
+
textShadow: e.sombra || '0 6px 22px rgba(0,0,0,.6), 0 2px 0 rgba(0,0,0,.35)',
|
|
345
|
+
WebkitTextStroke: e.contorno || '2px rgba(0,0,0,.55)',
|
|
346
|
+
paintOrder: 'stroke fill',
|
|
347
|
+
background: falando && e.fundoAtivo ? e.fundoAtivo : undefined,
|
|
348
|
+
borderRadius: '0.18em', padding: falando && e.fundoAtivo ? '0 0.12em' : undefined,
|
|
349
|
+
}}>{p.texto}{p.emoji ? ' ' + p.emoji : ''}</span>
|
|
350
|
+
);
|
|
351
|
+
})}
|
|
352
|
+
</div>
|
|
353
|
+
);
|
|
354
|
+
}
|
|
305
355
|
return (
|
|
306
356
|
<div style={{
|
|
307
357
|
...estilo,
|
|
@@ -366,7 +416,7 @@ export function Cena({ cena, dados }) {
|
|
|
366
416
|
</AbsoluteFill>
|
|
367
417
|
);
|
|
368
418
|
}
|
|
369
|
-
|
|
419
|
+
`;
|
|
370
420
|
|
|
371
421
|
const RAIZ_JSX = String.raw`import React from 'react';
|
|
372
422
|
import { AbsoluteFill, Audio, Sequence, staticFile, useCurrentFrame, useVideoConfig, interpolate } from 'remotion';
|
|
@@ -428,6 +478,7 @@ export const Video = () => {
|
|
|
428
478
|
<Audio
|
|
429
479
|
src={staticFile(plano.audio.trilha.src || plano.audio.trilha)}
|
|
430
480
|
volume={plano.audio.trilha.volume ?? 0.18}
|
|
481
|
+
loop={plano.audio.trilha.loop !== false}
|
|
431
482
|
/>
|
|
432
483
|
)}
|
|
433
484
|
{(plano.audio && plano.audio.efeitos ? plano.audio.efeitos : []).map((e, i) => (
|
|
@@ -438,7 +489,7 @@ export const Video = () => {
|
|
|
438
489
|
</AbsoluteFill>
|
|
439
490
|
);
|
|
440
491
|
};
|
|
441
|
-
|
|
492
|
+
`;
|
|
442
493
|
|
|
443
494
|
function rootJsx(plano) {
|
|
444
495
|
const f = plano.formato || {};
|
|
@@ -513,12 +564,25 @@ para este projeto, não para o PrimoCode.
|
|
|
513
564
|
* @param {object} op.plano
|
|
514
565
|
* @param {object} op.icones { "lucide:zap": "<svg…>" } já baixados
|
|
515
566
|
*/
|
|
567
|
+
const TSCONFIG = JSON.stringify({
|
|
568
|
+
compilerOptions: {
|
|
569
|
+
target: 'ES2018', module: 'commonjs', jsx: 'react-jsx', strict: false, noEmit: true,
|
|
570
|
+
lib: ['es2015', 'dom'], esModuleInterop: true, skipLibCheck: true, forceConsistentCasingInFileNames: true,
|
|
571
|
+
},
|
|
572
|
+
exclude: ['remotion.config.ts'],
|
|
573
|
+
}, null, 2) + '\n';
|
|
574
|
+
|
|
516
575
|
function montar({ pasta, titulo, plano, icones = {} }) {
|
|
517
576
|
const raiz = path.join(pasta, 'remotion');
|
|
518
577
|
const src = path.join(raiz, 'src');
|
|
519
578
|
|
|
520
579
|
escrever(path.join(raiz, 'package.json'), pkgJson(titulo || 'video'));
|
|
521
580
|
escrever(path.join(raiz, 'remotion.config.ts'), REMOTION_CONFIG);
|
|
581
|
+
/* O remotion.config.ts é TypeScript, e o Remotion 4 recusa renderizar sem
|
|
582
|
+
um tsconfig.json na raiz ("Could not find a tsconfig.json"). Medido em
|
|
583
|
+
25/09: o vídeo que saiu bom só saiu porque o modelo criou este arquivo à
|
|
584
|
+
mão depois do erro — um modelo grátis travaria ali. */
|
|
585
|
+
escrever(path.join(raiz, 'tsconfig.json'), TSCONFIG);
|
|
522
586
|
escrever(path.join(raiz, 'README.md'), readme(titulo || 'Vídeo', plano));
|
|
523
587
|
escrever(path.join(src, 'index.js'), INDEX_JS);
|
|
524
588
|
escrever(path.join(src, 'Root.jsx'), rootJsx(plano));
|