primocode 11.3.3 → 11.5.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/LICENSES/edit2-terceiros.md +31 -0
- package/LICENSES/models-dev-MIT.txt +25 -0
- package/README.md +2 -2
- package/editor/SKILL.md +24 -0
- package/editor/THIRD_PARTY_NOTICES.md +1 -0
- package/editor/assets/baixar.json +9 -1
- package/editor/helpers/df_limpar.py +29 -0
- package/editor/helpers/edit2.py +155 -0
- package/editor/helpers/enquadrar_camera.py +77 -0
- package/editor/helpers/face_layout.py +20 -0
- package/editor/helpers/face_mediapipe.py +147 -0
- package/editor/helpers/instalar_df.sh +7 -0
- package/editor/helpers/legenda_ass.py +124 -0
- package/editor/helpers/look_ffmpeg.py +105 -0
- package/editor/helpers/motor_palavras.py +157 -0
- package/editor/helpers/primeiro_passe.py +214 -0
- package/editor/helpers/som_edit2.py +119 -0
- package/editor/helpers/transcribe.py +31 -2
- package/editor/pyproject.toml +9 -0
- package/editor/references/edit2.md +22 -0
- package/editor/uv.lock +3342 -214
- package/kits/3d/LICENSES/README.md +11 -0
- package/kits/3d/animacoes.js +87 -0
- package/kits/3d/base.js +44 -0
- package/kits/3d/catalogo.js +38 -0
- package/kits/3d/cenario.js +162 -0
- package/kits/3d/exemplos/asfalto.mjs +91 -0
- package/kits/3d/maquinas.js +97 -0
- package/kits/3d/montador.js +142 -0
- package/kits/3d/package.json +1 -0
- package/kits/3d/personagem.js +86 -0
- package/kits/3d/remotion/Maquete.jsx +42 -0
- package/kits/3d/remotion/Root.jsx +10 -0
- package/kits/3d/render.mjs +73 -0
- package/lib/cli/comando-api.js +30 -8
- package/lib/cli/modelos-lista.js +58 -0
- package/lib/cli/sessao.js +11 -0
- package/lib/cli/subcomandos.js +3 -2
- package/lib/outra-pasta.js +1 -1
- package/lib/permissao-sistema.js +60 -0
- package/lib/permission/perigo.js +2 -1
- package/lib/permission/permissao.js +1 -0
- package/lib/permission/sem-perguntar.js +29 -0
- package/lib/plugin/desktop.js +5 -4
- package/lib/plugin/estudio/cota.js +4 -2
- package/lib/plugin/estudio/dialogo3d.js +71 -0
- package/lib/plugin/estudio/effectcraft.js +1 -1
- package/lib/plugin/estudio/glifos.js +2 -0
- package/lib/plugin/estudio/gsap-local.js +43 -0
- package/lib/plugin/estudio/index.js +63 -10
- package/lib/plugin/estudio/kit-ae/base.jsx +1 -1
- package/lib/plugin/estudio/kit-ae/cenas.jsx +7 -4
- package/lib/plugin/estudio/kit-ae/destrava.jsx +211 -0
- package/lib/plugin/estudio/maquina-diretor.js +1 -1
- package/lib/plugin/estudio/maquina-elementos.js +2 -2
- package/lib/plugin/estudio/maquina.js +26 -8
- package/lib/plugin/estudio/motion-plano/ajustar.js +139 -0
- package/lib/plugin/estudio/motion-plano/critico.js +59 -0
- package/lib/plugin/estudio/motion-plano/estrutura.js +111 -0
- package/lib/plugin/estudio/motion-plano/fatos.js +137 -0
- package/lib/plugin/estudio/motion-plano/index.js +75 -0
- package/lib/plugin/estudio/motion-plano/llm.js +59 -0
- package/lib/plugin/estudio/motion-plano/motor.js +50 -0
- package/lib/plugin/estudio/motion-plano/passos.js +38 -0
- package/lib/plugin/estudio/motion-plano/pele.js +36 -0
- package/lib/plugin/estudio/motion-plano/plano.js +152 -0
- package/lib/plugin/estudio/motion-plano/regua.js +100 -0
- package/lib/plugin/estudio/personagem.js +3 -1
- package/lib/plugin/estudio/referencias.js +1 -1
- package/lib/provider/catalogo.js +161 -0
- package/lib/provider/local/catalogo-embutido.json.gz +0 -0
- package/lib/provider/local/cerebro.json +1 -1
- package/lib/provider/local/entrada.js +10 -0
- package/lib/provider/local/motor.js +140 -27
- package/lib/provider/local/orcamento.js +193 -0
- package/lib/provider/local/provedor.js +127 -14
- package/lib/provider/local/recorte.js +170 -0
- package/lib/provider/local/revezamento.js +121 -0
- package/lib/session/runner/act.js +28 -3
- package/lib/skill/catalogo.js +9 -1
- package/lib/tool/reparar-entrada.js +1 -0
- package/lib/tool/tools.js +58 -1
- package/lib/tui/ui.js +1 -1
- package/package.json +2 -2
- package/vendor/fontes/ttf/ORIGEM.txt +1 -0
- package/vendor/fontes/ttf/inter-800.ttf +0 -0
- package/vendor/gsap/ORIGEM.txt +6 -0
- package/vendor/gsap/gsap.min.js +11 -0
- package/voz/primo_voz/chat.html +206 -5
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* permissao-sistema.js — falta permissão do macOS: parar na PRIMEIRA falha.
|
|
3
|
+
*
|
|
4
|
+
* O que aconteceu: sem Acessibilidade/Gravação de Tela, o agente tentava abrir o
|
|
5
|
+
* navegador, falhava, e tentava de novo (5+ vezes) — cada volta com o mesmo erro.
|
|
6
|
+
* Aqui o erro é reconhecido, vira uma frase clara ("falta a permissão X") e o laço
|
|
7
|
+
* do agente (act.js) para na hora. No app desktop, a linha de protocolo
|
|
8
|
+
* `primocode:permissao <id>` no stderr abre a tela de permissões.
|
|
9
|
+
*
|
|
10
|
+
* Puro de propósito: sem I/O, o teste mede a política inteira.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
'use strict';
|
|
14
|
+
|
|
15
|
+
/** As ferramentas que mexem no computador: só nelas um erro vira "falta permissão". */
|
|
16
|
+
const FERRAMENTA_DE_COMPUTADOR = /^(desktop_|browser_|open_in_browser$)/;
|
|
17
|
+
|
|
18
|
+
const NOMES = { accessibility: 'Acessibilidade', screen: 'Gravação de Tela', automation: 'Automação' };
|
|
19
|
+
|
|
20
|
+
/** Texto de erro do macOS → qual permissão falta. A ordem importa: a mais específica primeiro. */
|
|
21
|
+
const PADROES = [
|
|
22
|
+
['automation', /-1743|not authori[sz]ed to send apple events|apple events? (is|are) not allowed/i],
|
|
23
|
+
['accessibility', /-25211|-1719|-1002|not allowed assistive access|assistive access|is not allowed to send keystrokes|osascript[^\n]*n(ã|a)o tem permiss(ã|a)o|acessibilidade/i],
|
|
24
|
+
['screen', /could not create image from display|screen ?recording|grava(ç|c)(ã|a)o de tela|not authori[sz]ed to capture screen/i],
|
|
25
|
+
];
|
|
26
|
+
|
|
27
|
+
function ehFerramentaDeComputador(nome) {
|
|
28
|
+
return FERRAMENTA_DE_COMPUTADOR.test(String(nome || ''));
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Qual permissão falta, ou null. Só vale no macOS e para resultado com ok === false. */
|
|
32
|
+
function detectar(nome, resultado, plataforma = process.platform) {
|
|
33
|
+
if (plataforma !== 'darwin' || !ehFerramentaDeComputador(nome)) return null;
|
|
34
|
+
if (!resultado || resultado.ok !== false) return null;
|
|
35
|
+
const texto = [resultado.error, resultado.stderr, resultado.message].filter(Boolean).join('\n');
|
|
36
|
+
if (!texto) return null;
|
|
37
|
+
for (const [id, re] of PADROES) if (re.test(texto)) return id;
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** A frase que volta pro modelo e pra pessoa. Diz o que fazer e proíbe tentar de novo. */
|
|
42
|
+
function mensagem(id) {
|
|
43
|
+
const nome = NOMES[id] || id;
|
|
44
|
+
return `Falta a permissão ${nome} do macOS. Abri a tela de permissões do PrimoCode: ative ${nome} e peça de novo. `
|
|
45
|
+
+ 'NÃO tente outra vez nem outra ferramenta de computador/navegador até a pessoa ativar.';
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const PROTOCOLO = 'primocode:permissao';
|
|
49
|
+
const linhaProtocolo = (id) => `${PROTOCOLO} ${id}\n`;
|
|
50
|
+
function idDaLinha(linha) {
|
|
51
|
+
const m = String(linha || '').trim().match(/^primocode:permissao (accessibility|screen|automation)$/);
|
|
52
|
+
return m ? m[1] : null;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** O resultado trocado: mesmo `ok:false`, erro claro e a marca `permissao`. */
|
|
56
|
+
function comoErro(id, resultado) {
|
|
57
|
+
return { ...resultado, ok: false, permissao: id, error: mensagem(id) };
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
module.exports = { detectar, mensagem, comoErro, linhaProtocolo, idDaLinha, ehFerramentaDeComputador, NOMES, PADROES, PROTOCOLO };
|
package/lib/permission/perigo.js
CHANGED
|
@@ -30,7 +30,7 @@ const trava = require('../trava');
|
|
|
30
30
|
|
|
31
31
|
const MODOS = ['auto', 'perguntar', 'livre', 'plano'];
|
|
32
32
|
const COMANDOS = new Set(['run_shell', 'run_in_new_terminal', 'run_background']);
|
|
33
|
-
const ESCRITAS = new Set(['write_file', 'edit_file', 'multi_edit']);
|
|
33
|
+
const ESCRITAS = new Set(['write_file', 'edit_file', 'multi_edit', 'notebook_edit']);
|
|
34
34
|
|
|
35
35
|
function normalizarModo(m) {
|
|
36
36
|
const s = String(m || '').trim().toLowerCase();
|
|
@@ -350,6 +350,7 @@ async function portao(estado, { modo, nome, args, projectDir, subagent = false,
|
|
|
350
350
|
const travado = trava.recusa(nome, args, projectDir);
|
|
351
351
|
if (travado) return { ok: false, motivo: travado.motivo, error: travado.error };
|
|
352
352
|
if (m === 'livre') return { ok: true };
|
|
353
|
+
if (m !== 'plano' && require('./sem-perguntar.js').ativo()) return { ok: true }; // --yolo: sem perguntas (o modo plano continua só lendo)
|
|
353
354
|
if (m === 'plano') {
|
|
354
355
|
const mexe = ESCRITAS.has(nome) || nome === 'mcp_conectar'
|
|
355
356
|
|| (COMANDOS.has(nome) && !somenteLeitura((args || {}).command));
|
|
@@ -93,6 +93,7 @@ function descrever(nome, args) {
|
|
|
93
93
|
*/
|
|
94
94
|
async function pedir(estado, nome, args, perguntar) {
|
|
95
95
|
if (!precisa(nome)) return { ok: true };
|
|
96
|
+
if (require('./sem-perguntar.js').ativo()) return { ok: true }; // --yolo: sem perguntas
|
|
96
97
|
if (estado.liberado) return { ok: true };
|
|
97
98
|
|
|
98
99
|
/* Negar uma vez vale para a sessão. Sem isto, quem disse não leva a mesma
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* sem-perguntar.js — o modo `--dangerously-skip-permissions` (alias `--yolo`).
|
|
3
|
+
*
|
|
4
|
+
* Com ele o PrimoCode não pede confirmação nenhuma: controle do computador,
|
|
5
|
+
* comando perigoso, troca de pasta, servidor MCP do projeto. Vale por variável
|
|
6
|
+
* de ambiente (PRIMOCODE_SEM_PERGUNTAR=1) para chegar aos subprocessos e ao
|
|
7
|
+
* motor do desktop. Sem a variável, nada muda.
|
|
8
|
+
*
|
|
9
|
+
* Duas coisas continuam valendo de propósito: o /lock (arquivo travado só se
|
|
10
|
+
* lê) e a recusa de escrever fora da pasta do projeto (lib/tool/tools.js).
|
|
11
|
+
*/
|
|
12
|
+
'use strict';
|
|
13
|
+
|
|
14
|
+
const FLAGS = ['--dangerously-skip-permissions', '--yolo'];
|
|
15
|
+
const AVISO = 'modo sem perguntas: o PrimoCode vai agir sem pedir confirmação';
|
|
16
|
+
|
|
17
|
+
function ativo() { return process.env.PRIMOCODE_SEM_PERGUNTAR === '1'; }
|
|
18
|
+
|
|
19
|
+
/** Tira a flag do argv (muta) e liga a variável. Devolve true se estava lá. */
|
|
20
|
+
function consumir(argv) {
|
|
21
|
+
let achou = false;
|
|
22
|
+
for (let i = argv.length - 1; i >= 2; i--) {
|
|
23
|
+
if (FLAGS.includes(argv[i])) { argv.splice(i, 1); achou = true; }
|
|
24
|
+
}
|
|
25
|
+
if (achou) process.env.PRIMOCODE_SEM_PERGUNTAR = '1';
|
|
26
|
+
return achou;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
module.exports = { ativo, consumir, FLAGS, AVISO };
|
package/lib/plugin/desktop.js
CHANGED
|
@@ -35,12 +35,13 @@ const TOOLS = {
|
|
|
35
35
|
function run(cmd, opts = {}) {
|
|
36
36
|
try {
|
|
37
37
|
execSync(cmd, {
|
|
38
|
-
stdio: 'ignore',
|
|
38
|
+
stdio: ['ignore', 'ignore', 'pipe'],
|
|
39
39
|
timeout: opts.timeout || 15000,
|
|
40
40
|
env: opts.env ? { ...process.env, ...opts.env } : undefined,
|
|
41
41
|
});
|
|
42
42
|
return { ok: true };
|
|
43
|
-
|
|
43
|
+
// o stderr entra no erro: é nele que o macOS diz "falta permissão" (lib/permissao-sistema.js)
|
|
44
|
+
} catch (e) { return { ok: false, error: [e.message, String((e && e.stderr) || '').trim()].filter(Boolean).join('\n') }; }
|
|
44
45
|
}
|
|
45
46
|
|
|
46
47
|
// Igual ao run(), mas devolve o stdout — para os comandos que PERGUNTAM algo
|
|
@@ -49,12 +50,12 @@ function runOut(cmd, opts = {}) {
|
|
|
49
50
|
try {
|
|
50
51
|
const out = execSync(cmd, {
|
|
51
52
|
encoding: 'utf-8',
|
|
52
|
-
stdio: ['ignore', 'pipe', '
|
|
53
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
53
54
|
timeout: opts.timeout || 15000,
|
|
54
55
|
env: opts.env ? { ...process.env, ...opts.env } : undefined,
|
|
55
56
|
});
|
|
56
57
|
return { ok: true, out: String(out || '') };
|
|
57
|
-
} catch (e) { return { ok: false, error: e.message, out: String((e && e.stdout) || '') }; }
|
|
58
|
+
} catch (e) { return { ok: false, error: [e.message, String((e && e.stderr) || '').trim()].filter(Boolean).join('\n'), out: String((e && e.stdout) || '') }; }
|
|
58
59
|
}
|
|
59
60
|
|
|
60
61
|
// Argumento de shell entre aspas simples, à prova de apóstrofo. O truque
|
|
@@ -143,10 +143,12 @@ async function comCota(render, deps = {}) {
|
|
|
143
143
|
const r = await render();
|
|
144
144
|
const extra = lim.pegarAvisos();
|
|
145
145
|
if (!r || !r.ok) return r;
|
|
146
|
-
|
|
146
|
+
// a marca d'água do PrimoCode só entra no vídeo do usuário se ELE pediu (Joel, 13/11): `deps.marca` vem do pedido; o teto de resolução segue valendo
|
|
147
|
+
const rec = deps.marca ? c.recursos : { ...c.recursos, marcaDagua: false };
|
|
148
|
+
const a = c.offline ? { ok: true, mudou: false } : (deps.aplicar || aplicar)(r.video, rec);
|
|
147
149
|
if (!c.offline) await (deps.consultar || consultar)('gastar');
|
|
148
150
|
const restam = c.restam == null ? null : Math.max(0, c.restam - 1);
|
|
149
|
-
return { ...r, plano: { id: c.plano, restam, resolucao: c.recursos.resolucao, marcaDagua: !!(a.mudou &&
|
|
151
|
+
return { ...r, plano: { id: c.plano, restam, resolucao: c.recursos.resolucao, marcaDagua: !!(a.mudou && rec.marcaDagua) },
|
|
150
152
|
...((() => {
|
|
151
153
|
const a = [...(c.offline ? ['sem rede: modo local, sem cota nem marca d\'água.'] : []),
|
|
152
154
|
...(!c.offline && restam === 0 ? ['Foi o seu último vídeo do mês. Veja os planos com: primocode planos'] : []), ...extra];
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dialogo3d.js — os personagens falando, pela MESMA estrutura da maquete (kits/3d).
|
|
3
|
+
*
|
|
4
|
+
* Pega a partitura que o personagem.js já faz (quem fala, quando, o lugar, o elenco)
|
|
5
|
+
* e a traduz num roteiro por etapas para o montador: uma etapa por fala, a câmera
|
|
6
|
+
* no falante, a boca na palavra, o gesto da fala. Versão SIMPLES: sem cartões, sem
|
|
7
|
+
* planos de filme. O caminho antigo (kit/personagem.jsx) continua intacto; este só
|
|
8
|
+
* entra quando o roteiro pede `motor: 'montador'`.
|
|
9
|
+
*
|
|
10
|
+
* Render: `node kits/3d/render.mjs roteiro3d.json saida.mp4 --audio voz.mp3 --audio-pronto`.
|
|
11
|
+
*/
|
|
12
|
+
'use strict';
|
|
13
|
+
|
|
14
|
+
// A animação da biblioteca para o gesto que o personagem.js escolheu (acaoDe) e a emoção.
|
|
15
|
+
const GESTO = { falar: 'falar', acenar: 'falar', apontar: 'apontar', pular: 'comemorar', dancar: 'comemorar', comemorar: 'comemorar', pensar: 'pensar', andar: 'falar', correr: 'falar', sentar: 'falar' };
|
|
16
|
+
const EMOCAO = { surpreso: 'surpreso', pensativo: 'pensar' };
|
|
17
|
+
|
|
18
|
+
const r2 = (x) => Math.round(x * 100) / 100;
|
|
19
|
+
const XS = { 1: [0], 2: [-1.25, 1.25], 3: [-2.2, 0, 2.2], 4: [-3.3, -1.1, 1.1, 3.3] };
|
|
20
|
+
|
|
21
|
+
/** A aparência de um membro do elenco na biblioteca (boneco único; robô e bicho viram cor). */
|
|
22
|
+
function pecaDoElenco(e, i, n) {
|
|
23
|
+
const x = XS[Math.min(4, Math.max(1, n))][i];
|
|
24
|
+
const base = { id: `p${i}`, tipo: 'personagem', pos: [x, 0, 0], giro: r2(-x * 0.16), escala: 1 };
|
|
25
|
+
if (e.tipo === 'pessoa') return { ...base, pele: e.pele, roupa: e.roupa, cabelo: e.corCabelo };
|
|
26
|
+
if (e.tipo === 'robo') return { ...base, pele: e.cor || '#dd5a2b', roupa: '#cfd6df', calca: '#8b95a3', capacete: e.cor || '#dd5a2b' };
|
|
27
|
+
return { ...base, pele: e.cor || '#c8a27a', roupa: e.cor || '#c8a27a', escala: 0.85 }; // bicho/coisa: boneco na cor dele
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** O lugar (mundo.js) em peças da biblioteca: chão, árvores, casas e prédios. O resto do catálogo antigo fica de fora. */
|
|
31
|
+
function cenarioDoMundo(m) {
|
|
32
|
+
const pecas = [{ id: 'chao', tipo: 'terreno', w: 80, d: 80, cor: (m.chao && m.chao.cor) || '#b99362' }];
|
|
33
|
+
(m.props || []).forEach((p, i) => {
|
|
34
|
+
const base = { id: `c${i}`, pos: [p.x, 0, Math.min(p.z, -2.5)], giro: p.giro || 0 };
|
|
35
|
+
if (/^(arvore|palmeira|arbusto)$/.test(p.peca)) pecas.push({ ...base, tipo: 'arvore', escala: p.escala || 1, forma: 'redonda' });
|
|
36
|
+
else if (/^pinheiro/.test(p.peca)) pecas.push({ ...base, tipo: 'arvore', escala: p.escala || 1, forma: 'pinheiro' });
|
|
37
|
+
else if (p.peca === 'casa' || p.peca === 'celeiro') pecas.push({ ...base, tipo: 'casa', escala: p.escala || 1 });
|
|
38
|
+
else if (p.peca === 'predio') pecas.push({ ...base, tipo: 'predio', andares: 4 + (i % 4), pos: [p.x, 0, Math.min(p.z, -6)] });
|
|
39
|
+
});
|
|
40
|
+
return pecas;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* @param {object} partitura a saída de personagem.partituraDe
|
|
45
|
+
* @returns {object} roteiro do montador (JSON puro)
|
|
46
|
+
*/
|
|
47
|
+
function roteiroDeDialogo(partitura) {
|
|
48
|
+
const elenco = partitura.elenco, falas = partitura.falas;
|
|
49
|
+
const pecas = elenco.map((e, i) => pecaDoElenco(e, i, elenco.length));
|
|
50
|
+
const mundo = (partitura.mundos && partitura.mundos[0]) || {};
|
|
51
|
+
const ceu = mundo.dentro ? [mundo.parede || '#e6d6c3', mundo.parede || '#e6d6c3'] : (mundo.ceu || ['#8fd0ff', '#d8efff']);
|
|
52
|
+
const etapas = falas.map((f, i) => {
|
|
53
|
+
const ini = i === 0 ? 0 : r2(falas[i].de - 0.25);
|
|
54
|
+
const fim = i === falas.length - 1 ? partitura.duracao : r2(falas[i + 1].de - 0.25);
|
|
55
|
+
const dur = r2(Math.max(0.5, fim - ini)), x = pecas[f.quem].pos[0];
|
|
56
|
+
const amplo = i === 0 || (elenco.length > 1 && i % 4 === 0);
|
|
57
|
+
const gesto = EMOCAO[f.emocao] || GESTO[f.acao] || 'falar';
|
|
58
|
+
return {
|
|
59
|
+
id: `fala${i}`, dur, titulo: elenco[f.quem].rotulo || elenco[f.quem].nome, legenda: f.texto, inicio: ini,
|
|
60
|
+
voz: { de: f.de, ate: f.ate }, vozEm: r2(f.de - ini),
|
|
61
|
+
camera: amplo ? { de: [0, 2.2, 10.2], para: [0, 2.0, 9.4], alvo: [0, 1.0, 0], fov: 40 }
|
|
62
|
+
: { de: [x * 0.55, 1.6, 5.2], para: [x * 0.55, 1.55, 4.5], alvo: [x, 1.25, 0], fov: 38 },
|
|
63
|
+
pecas: i === 0 ? [...(mundo ? cenarioDoMundo(mundo) : []), ...pecas] : [],
|
|
64
|
+
acoes: [{ em: 0, dur, quem: `p${f.quem}`, faz: 'anim', nome: gesto }],
|
|
65
|
+
falas: [{ quem: `p${f.quem}`, de: r2(f.de - ini), ate: r2(f.ate - ini), palavras: (f.palavras || []).map((w) => ({ de: r2(w.de - ini), ate: r2(w.ate - ini) })) }],
|
|
66
|
+
};
|
|
67
|
+
});
|
|
68
|
+
return { ceu, horizonte: (mundo.chao && mundo.chao.cor) || '#8fc46b', modo: 'dialogo', etapas };
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
module.exports = { roteiroDeDialogo, cenarioDoMundo, pecaDoElenco, GESTO };
|
|
@@ -108,7 +108,7 @@ async function renderizar({ pasta, script, comp, fontes = [], audio, conferir =
|
|
|
108
108
|
if (fontes && fontes.length) {
|
|
109
109
|
const glifos = require('./glifos.js');
|
|
110
110
|
for (const f of fontes) {
|
|
111
|
-
try {
|
|
111
|
+
try { const arq = await glifos.baixarFonte(f.familia, f.peso || 400); if (arq) dirs.push(path.dirname(arq)); } catch { /* sem rede: segue com a fonte do sistema */ }
|
|
112
112
|
}
|
|
113
113
|
}
|
|
114
114
|
if (dirs.length) env.EFFECTCRAFT_FONT_DIRS = [...new Set(dirs)].join(path.delimiter);
|
|
@@ -191,6 +191,8 @@ const CACHE = path.join(os.homedir(), '.primocode', 'studio', 'cache-fontes');
|
|
|
191
191
|
|
|
192
192
|
async function baixarFonte(familia, peso = 700) {
|
|
193
193
|
const nome = String(familia).replace(/[^a-z0-9]+/gi, '-').toLowerCase() + '-' + peso + '.ttf';
|
|
194
|
+
const embutida = path.join(__dirname, '..', '..', '..', 'vendor', 'fontes', 'ttf', nome); // as embutidas (Inter 800 da pele do Destrava) não dependem de rede nem do cache
|
|
195
|
+
if (fs.existsSync(embutida)) return embutida;
|
|
194
196
|
const arq = path.join(CACHE, nome);
|
|
195
197
|
if (fs.existsSync(arq)) return arq;
|
|
196
198
|
// A API antiga (css, não css2) devolve .ttf; a nova devolve woff2, que não tem a glyf crua.
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* gsap-local.js — o GSAP vem de um arquivo versionado (vendor/gsap/gsap.min.js), nunca de CDN.
|
|
3
|
+
*
|
|
4
|
+
* `embutir(pastaDaComposicao)` roda antes do render do Hyperframes: troca qualquer <script src> de CDN do núcleo do
|
|
5
|
+
* GSAP pelo arquivo local, e, se o HTML usa `gsap.` sem carregar nada, injeta o script. Puro no texto (`trocar`),
|
|
6
|
+
* com efeito só em `embutir` (copia o arquivo e regrava o index.html).
|
|
7
|
+
*/
|
|
8
|
+
'use strict';
|
|
9
|
+
const fs = require('fs');
|
|
10
|
+
const path = require('path');
|
|
11
|
+
|
|
12
|
+
const ARQUIVO = path.join(__dirname, '..', '..', '..', 'vendor', 'gsap', 'gsap.min.js');
|
|
13
|
+
const DESTINO_REL = 'vendor/gsap.min.js';
|
|
14
|
+
// <script ... src="...gsap(.min).js..."> do núcleo (não pega ScrollTrigger.min.js, MotionPathPlugin etc.)
|
|
15
|
+
const CDN = /<script\b([^>]*?)\bsrc\s*=\s*(["'])(https?:)?\/\/[^"']*?\/gsap(?:@[^/"']*)?(?:\/dist)?(?:\/gsap)?(?:\.min)?\.js(?:\?[^"']*)?\2([^>]*)><\/script>/gi;
|
|
16
|
+
|
|
17
|
+
/** Puro. Devolve { html, trocou, injetou }. */
|
|
18
|
+
function trocar(html) {
|
|
19
|
+
let trocou = 0;
|
|
20
|
+
let saida = String(html || '').replace(CDN, (_m, antes, q, _p, depois) => { trocou++; return `<script${antes}src=${q}${DESTINO_REL}${q}${depois}></script>`; });
|
|
21
|
+
const jaTem = new RegExp(`src\\s*=\\s*["'][^"']*gsap[^"']*\\.js`, 'i').test(saida);
|
|
22
|
+
let injetou = false;
|
|
23
|
+
if (!jaTem && /\bgsap\s*\./.test(saida)) {
|
|
24
|
+
const tag = `<script src="${DESTINO_REL}"></script>`;
|
|
25
|
+
saida = /<head[^>]*>/i.test(saida) ? saida.replace(/<head[^>]*>/i, (h) => `${h}\n${tag}`) : /<body[^>]*>/i.test(saida) ? saida.replace(/<body[^>]*>/i, (b) => `${b}\n${tag}`) : `${tag}\n${saida}`;
|
|
26
|
+
injetou = true;
|
|
27
|
+
}
|
|
28
|
+
return { html: saida, trocou, injetou };
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Efeito: copia o arquivo e regrava o index.html da pasta. Devolve { ok, trocou, injetou, erro? }. */
|
|
32
|
+
function embutir(pasta) {
|
|
33
|
+
const idx = path.join(pasta, 'index.html');
|
|
34
|
+
if (!fs.existsSync(idx)) return { ok: false, erro: 'sem index.html' };
|
|
35
|
+
const r = trocar(fs.readFileSync(idx, 'utf8'));
|
|
36
|
+
if (!r.trocou && !r.injetou) return { ok: true, trocou: 0, injetou: false };
|
|
37
|
+
fs.mkdirSync(path.join(pasta, 'vendor'), { recursive: true });
|
|
38
|
+
fs.copyFileSync(ARQUIVO, path.join(pasta, DESTINO_REL));
|
|
39
|
+
fs.writeFileSync(idx, r.html);
|
|
40
|
+
return { ok: true, trocou: r.trocou, injetou: r.injetou };
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
module.exports = { trocar, embutir, ARQUIVO, DESTINO_REL };
|
|
@@ -674,7 +674,7 @@ async function ferramentaAE(args) {
|
|
|
674
674
|
const p = args.id ? projeto.ler(args.id) : null;
|
|
675
675
|
if (!args.id || !p) return semProjeto(args.id);
|
|
676
676
|
if (!args.script || !args.comp) return { ok: false, error: 'estudio_ae pede script (o JSX) e comp (o nome da composição).' };
|
|
677
|
-
return require('./cota.js').comCota(() => renderAE(args, p));
|
|
677
|
+
return require('./cota.js').comCota(() => renderAE(args, p), { marca: args.marcaDagua === true });
|
|
678
678
|
}
|
|
679
679
|
|
|
680
680
|
async function renderAE(args, p) {
|
|
@@ -707,7 +707,7 @@ function entregar(args, p, r) {
|
|
|
707
707
|
}
|
|
708
708
|
|
|
709
709
|
/** A máquina de motion: assunto + cenas em JSON viram o mp4 (maquina.js), com sons, trilha e voz opcionais. */
|
|
710
|
-
async function ferramentaMotion(args) {
|
|
710
|
+
async function ferramentaMotion(args, ctx = {}, deps = {}) {
|
|
711
711
|
const p = args.id ? projeto.ler(args.id) : null;
|
|
712
712
|
if (!args.id || !p) return semProjeto(args.id);
|
|
713
713
|
let receita = null;
|
|
@@ -721,10 +721,29 @@ async function ferramentaMotion(args) {
|
|
|
721
721
|
// primeiro motion: aprende o gosto antes de renderizar (a pessoa pode fechar ou pular; o render segue de qualquer jeito)
|
|
722
722
|
// no terminal abre o navegador; no app (desktop ou Primo.app, -p com PRIMOCODE_APP=1) o app abre a tela pela linha de protocolo
|
|
723
723
|
try { await require('./gosto.js').antesDoMotion(args); } catch { /* sem tela, segue */ }
|
|
724
|
-
|
|
724
|
+
// As etapas na frente do motor (motion-plano): ajusta o pedido, estrutura, monta o plano; o plano vira `cenas`.
|
|
725
|
+
// `etapas:false` ou PRIMOCODE_MOTION_ETAPAS=0 liga o caminho antigo. Falhou e não é estrito: segue pelo antigo, com aviso.
|
|
726
|
+
const MP = require('./motion-plano/index.js');
|
|
727
|
+
let etapas = null, avisoEtapas = null;
|
|
728
|
+
if (MP.ligado(args)) {
|
|
729
|
+
const fotosDoPedido = [...new Set([...(Array.isArray(args.fotos) ? args.fotos : args.fotos ? [args.fotos] : []), ...referencias.imagens()])];
|
|
730
|
+
const r = await MP.preparar(args, { escolher: ctx.escolher, linha: ctx.linha }, { llm: deps.llm, pasta: p.pasta, fotos: fotosDoPedido });
|
|
731
|
+
if (r.ok) { args = r.args; etapas = r.etapas; }
|
|
732
|
+
else if (deps.estrito || args.estrito) return { ok: false, error: r.error, etapa: r.etapa, passos: r.passos };
|
|
733
|
+
else avisoEtapas = `As etapas (ajustar, estruturar, planejar) não rodaram: ${r.error} Segui pelo caminho antigo.`;
|
|
734
|
+
}
|
|
735
|
+
const saida = await require('./cota.js').comCota(() => renderMotion(args, p, receita, deps.motor), { marca: args.marcaDagua === true });
|
|
736
|
+
if (!saida || saida.ok === false) return saida;
|
|
737
|
+
const extra = {};
|
|
738
|
+
if (etapas) {
|
|
739
|
+
extra.etapas = { briefing: etapas.briefing, plano: etapas.plano, respostas: etapas.respostas };
|
|
740
|
+
extra.critico = MP.critico(saida.video, p.pasta);
|
|
741
|
+
}
|
|
742
|
+
if (avisoEtapas) extra.avisos = [...(saida.avisos || []), avisoEtapas];
|
|
743
|
+
return { ...saida, ...extra };
|
|
725
744
|
}
|
|
726
745
|
|
|
727
|
-
async function renderMotion(args, p, receita) {
|
|
746
|
+
async function renderMotion(args, p, receita, depsMotor = {}) {
|
|
728
747
|
const narrar = async (texto, mp3, voz) => rodarPython('narrar.py', ['falar', '--texto', texto, '--saida', mp3, '--voz', String(voz || 'francisca')], { timeout: 5 * 60000 });
|
|
729
748
|
// Kit de marca ativo: cores, fontes e logo entram onde o JSON não disse; o logo vai no CTA (última cena).
|
|
730
749
|
const kitM = marcaKit.preencher(args.marca, { pastas: [p.pasta, process.cwd()] });
|
|
@@ -734,13 +753,16 @@ async function renderMotion(args, p, receita) {
|
|
|
734
753
|
args.cenas = args.cenas.map((c, i) => (i === args.cenas.length - 1 && c && !c.logo ? { ...c, logo: kitM.logo } : c));
|
|
735
754
|
}
|
|
736
755
|
}
|
|
756
|
+
// Com o plano ativo e sem cor de marca nem estilo pedido, a pele é a do Destrava (creme, Inter 800, degradê rosa → violeta).
|
|
757
|
+
const pele = require('./motion-plano/pele.js');
|
|
758
|
+
if (pele.deveUsar(args, kitM)) args = pele.aplicar(args);
|
|
737
759
|
// As fotos do pedido: as que a IA passou em `fotos` e as que o terminal achou no texto do pedido (referencias.imagens()).
|
|
738
760
|
// Antes só o prepararRoteiro (Remotion) as lia: no motion elas viravam "referência" e nenhuma peça aceitava foto.
|
|
739
761
|
const doPedido = referencias.imagens();
|
|
740
762
|
const dadas = Array.isArray(args.fotos) ? args.fotos : (args.fotos ? [args.fotos] : []);
|
|
741
763
|
const todas = [...new Set([...dadas, ...doPedido])];
|
|
742
764
|
if (todas.length) args = { ...args, fotos: todas, ...(dadas.length ? {} : { fotosImplicitas: true }) };
|
|
743
|
-
const r = await require('./maquina.js').motion(args, { pasta: p.pasta, receita, deps: { narrar } });
|
|
765
|
+
const r = await require('./maquina.js').motion(args, { pasta: p.pasta, receita, deps: { narrar, ...depsMotor } });
|
|
744
766
|
if (r.ok) projeto.anotar(p.id, 'motion', `${r.duracao}s, ${r.cenas.length} cenas, ${r.lufs} LUFS`);
|
|
745
767
|
if (r.ok) entregar(args, p, r);
|
|
746
768
|
if (!r.ok) return r;
|
|
@@ -2413,16 +2435,47 @@ async function transcrever(p, alvo, ctx) {
|
|
|
2413
2435
|
escondido — sem telemetria (HYPERFRAMES_NO_TELEMETRY). Depois, o mesmo do
|
|
2414
2436
|
Remotion: volume de Reels, a conferência e o vídeo aberto. */
|
|
2415
2437
|
const HYPERFRAMES = 'hyperframes@0.8.154';
|
|
2416
|
-
|
|
2438
|
+
/* Acha a composição que a IA escreveu. O contrato é "uma pasta com index.html", mas o modelo erra de três jeitos:
|
|
2439
|
+
passa o caminho do próprio .html, passa o HTML no lugar do caminho, ou deixa o HTML num bloco ```html no argumento.
|
|
2440
|
+
Os três viram a pasta certa em vez de um erro técnico. Devolve { origem } ou { erro }. */
|
|
2441
|
+
function acharComposicao(p, args) {
|
|
2417
2442
|
const pedido = String(args.html || args.composicao || 'hyperframes').trim();
|
|
2418
|
-
const
|
|
2419
|
-
|
|
2420
|
-
if (
|
|
2443
|
+
const candidatos = [path.resolve(p.pasta, pedido), args.__pasta && path.resolve(args.__pasta, pedido), path.isAbsolute(pedido) && pedido].filter(Boolean);
|
|
2444
|
+
const pasta = candidatos.find((d) => fs.existsSync(path.join(d, 'index.html')));
|
|
2445
|
+
if (pasta) return { origem: pasta };
|
|
2446
|
+
// o caminho do próprio arquivo .html: usa a pasta dele (se não for index.html, copia como index.html)
|
|
2447
|
+
const arquivo = candidatos.find((f) => /\.html?$/i.test(f) && fs.existsSync(f) && fs.statSync(f).isFile());
|
|
2448
|
+
if (arquivo) {
|
|
2449
|
+
if (/^index\.html?$/i.test(path.basename(arquivo))) return { origem: path.dirname(arquivo) };
|
|
2450
|
+
const dir = path.join(p.pasta, 'hyperframes-entrada');
|
|
2451
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
2452
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
2453
|
+
fs.copyFileSync(arquivo, path.join(dir, 'index.html'));
|
|
2454
|
+
return { origem: dir };
|
|
2455
|
+
}
|
|
2456
|
+
// o HTML veio no lugar do caminho (começa com <, ou num bloco de código)
|
|
2457
|
+
const cerca = /```(?:html)?\s*([\s\S]*?)```/i.exec(pedido);
|
|
2458
|
+
const texto = (cerca ? cerca[1] : pedido).trim();
|
|
2459
|
+
if (/^<(!doctype|html|div|section|main|body|style)/i.test(texto)) {
|
|
2460
|
+
const dir = path.join(p.pasta, 'hyperframes-entrada');
|
|
2461
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
2462
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
2463
|
+
fs.writeFileSync(path.join(dir, 'index.html'), texto);
|
|
2464
|
+
return { origem: dir };
|
|
2465
|
+
}
|
|
2466
|
+
return { erro: `não achei a composição em "${pedido.slice(0, 80)}". Salve o HTML num arquivo index.html dentro de uma pasta (write_file) e chame estudio_render de novo com html:"<pasta>".` };
|
|
2467
|
+
}
|
|
2468
|
+
|
|
2469
|
+
async function renderHyperframes(p, args, ctx = {}) {
|
|
2470
|
+
const achada = acharComposicao(p, args);
|
|
2471
|
+
if (achada.erro) return { ok: false, error: achada.erro };
|
|
2472
|
+
const origem = achada.origem;
|
|
2421
2473
|
const destino = path.join(p.pasta, 'hyperframes');
|
|
2422
2474
|
if (path.resolve(origem) !== path.resolve(destino)) {
|
|
2423
2475
|
fs.rmSync(destino, { recursive: true, force: true });
|
|
2424
2476
|
fs.cpSync(origem, destino, { recursive: true });
|
|
2425
2477
|
}
|
|
2478
|
+
try { require('./gsap-local.js').embutir(destino); } catch { /* sem o troca, o Hyperframes tenta como veio */ } // GSAP local, nunca CDN
|
|
2426
2479
|
const midia = path.join(destino, 'midia');
|
|
2427
2480
|
if (!fs.existsSync(midia) && fs.existsSync(path.join(p.pasta, 'midia'))) fs.symlinkSync(path.join(p.pasta, 'midia'), midia);
|
|
2428
2481
|
const saida = path.join(p.pasta, 'saida', `${projeto.apelidar(p.titulo)}.mp4`);
|
|
@@ -2717,7 +2770,7 @@ async function ferramentaSite(args) {
|
|
|
2717
2770
|
return r;
|
|
2718
2771
|
}
|
|
2719
2772
|
|
|
2720
|
-
module.exports = { proporcaoDoPedido,
|
|
2773
|
+
module.exports = { proporcaoDoPedido, acharComposicao,
|
|
2721
2774
|
vazamentoNoComeco, variarIcones, objetosDoRoteiro,
|
|
2722
2775
|
ferramentaProjeto, ferramentaPlano, ferramentaMidia, ferramentaAE, ferramentaMotion, ferramentaEditar, ferramentaNarrar, ferramentaCriar,
|
|
2723
2776
|
ferramentaVideo, ferramentaRender, ferramentaSite,
|
|
@@ -235,7 +235,7 @@ function kEstiloCamera(cam, d) {
|
|
|
235
235
|
1) Glow (brilho do que é claro), 2) um vazamento de luz suave (a aberração cromática VR foi tirada: deixava uma faixa de 1–2% na borda direita e na base) (mancha de cor
|
|
236
236
|
borrada em ADD, varrendo o quadro). A energia vem do movimento e da câmera, não de entulho. */
|
|
237
237
|
function kAtmosfera(comp, P) {
|
|
238
|
-
if (KIT.fx === false) return;
|
|
238
|
+
if (KIT.fx === false || KIT.pele == 'destrava') return; // a pele do Destrava é creme limpo: sem vazamento rosa em ADD nem glow por cima
|
|
239
239
|
var W = comp.width, H = comp.height, u = Math.min(W, H) / 1080, pun = KIT.punch || [], cod = kPunchCodigo(pun), d = comp.duration;
|
|
240
240
|
try { // 3) vazamento de luz: uma mancha, sem pulso
|
|
241
241
|
var lk = comp.layers.addShape(); lk.name = 'vazamento de luz';
|
|
@@ -369,6 +369,8 @@ var CENAS = {
|
|
|
369
369
|
parede_logos: cenaParedeLogos, prompt: cenaPrompt, odometro: cenaOdometro, medidor: cenaMedidor, carimbo: cenaCarimbo, leque: cenaLeque, ondas: cenaOndas,
|
|
370
370
|
rolagem: cenaRolagem, estrelas: cenaEstrelas, foto: cenaFoto
|
|
371
371
|
};
|
|
372
|
+
// as peças do Destrava (destrava.jsx): o plano de motion marca a cena com d.dz
|
|
373
|
+
var CENAS_DZ = { frase: cenaDFrase, cor: cenaDCor, foto: cenaDFoto, marca: cenaDMarca };
|
|
372
374
|
|
|
373
375
|
function montar(PL) {
|
|
374
376
|
var W = PL.w, H = PL.h, fps = PL.fps || 30, n = PL.cenas.length, i;
|
|
@@ -376,8 +378,9 @@ function montar(PL) {
|
|
|
376
378
|
KIT.marca = { titulo: PL.fontes.titulo, destaque: PL.fontes.destaque, texto: PL.fontes.texto };
|
|
377
379
|
KIT.tipo = PL.tipo || {};
|
|
378
380
|
KIT.elementos = PL.elementos || [];
|
|
381
|
+
KIT.fotos = PL.fotos || [];
|
|
379
382
|
KIT.voz = PL.voz || null;
|
|
380
|
-
KIT.paletaBase = PL.paleta || null; KIT.debugFit = !!PL.debugFit; KIT.bpm = PL.bpm || 0; KIT.batidaFase = PL.batidaFase || 0; KIT.fx = PL.fx !== false;
|
|
383
|
+
KIT.paletaBase = PL.paleta || null; KIT.pele = PL.pele || null; KIT.debugFit = !!PL.debugFit; KIT.bpm = PL.bpm || 0; KIT.batidaFase = PL.batidaFase || 0; KIT.fx = PL.fx !== false;
|
|
381
384
|
// a câmera dá zoom de até ~10%: o conteúdo é montado a 90% (87% com legenda, subido 7%) para NÃO sair da área segura quando a câmera chega perto
|
|
382
385
|
KIT.enq = 0.88; KIT.enqDy = 0;
|
|
383
386
|
if (PL.legenda && PL.legenda.chunks && PL.legenda.chunks.length) { KIT.enq = 0.85; KIT.enqDy = H * 0.07; }
|
|
@@ -387,7 +390,7 @@ function montar(PL) {
|
|
|
387
390
|
var main = novaComp('MAIN', W, H, total, fps);
|
|
388
391
|
KIT.main = main;
|
|
389
392
|
// o fundo da MAIN é a cor de destaque: se as duas cenas se afastarem numa troca (chicote, empurra), o vão é uma faixa de cor, nunca preto
|
|
390
|
-
main.layers.addSolid(kCor(PL.paleta && PL.paleta.destaque ? PL.paleta.destaque : '#FFFFFF'), 'fundo-main', W, H, 1);
|
|
393
|
+
main.layers.addSolid(kCor(PL.pele == 'destrava' && PL.paleta && PL.paleta.fundo ? PL.paleta.fundo : (PL.paleta && PL.paleta.destaque ? PL.paleta.destaque : '#FFFFFF')), 'fundo-main', W, H, 1);
|
|
391
394
|
// um fundo por cena, embaixo de tudo, com a cor DA CENA: se a câmera ou a troca afastar a cena, a borda mostra a cor dela, não uma faixa de outra cor
|
|
392
395
|
for (i = 0; i < n; i++) { var fb = main.layers.addSolid(kCor(PL.cenas[i].pal.bg), 'fundo-cena ' + i, W, H, 1); fb.inPoint = starts[i]; fb.outPoint = i < n - 1 ? Math.min(total, starts[i + 1] + TROCA_DUR + 0.05) : total; }
|
|
393
396
|
var cam = [];
|
|
@@ -396,13 +399,13 @@ function montar(PL) {
|
|
|
396
399
|
// a cena é montada em tempo NOMINAL (dur × f) e tocada f vezes mais rápido dentro de uma comp-embrulho: tudo entra nos primeiros ~35%
|
|
397
400
|
var comp = novaComp('CENA ' + i, W, H, dur * f, fps);
|
|
398
401
|
// enfeite só onde há espaço livre de verdade (nada de herói em cima de janela, prompt ou botão)
|
|
399
|
-
KIT.cenaComElemento = !!c.elemento; KIT.semAmbiente = !!c.semAmbiente || !{ cinetico: 1, gancho: 1, numero: 1, citacao: 1, particulas: 1, odometro: 1, relogio: 1, carimbo: 1 }[c.tipo];
|
|
402
|
+
KIT.cenaComElemento = !!c.elemento; KIT.semAmbiente = !!c.semAmbiente || !!c.dz || !{ cinetico: 1, gancho: 1, numero: 1, citacao: 1, particulas: 1, odometro: 1, relogio: 1, carimbo: 1 }[c.tipo];
|
|
400
403
|
// (até 20/10 esta linha morava DEPOIS do comentário acima, na mesma linha: o cenaIdx, o t0, o ajuste e o f nunca eram postos)
|
|
401
404
|
KIT.semRotulos = !!c.semRotulos; KIT.cenaHeroi = c.tipo == 'heroi' || c.tipo == 'revela'; KIT.titulosL = []; KIT.tituloB = null; KIT.t0 = starts[i]; KIT.cenaIdx = i; KIT.estilo = c.camera || PL.camera || 'deriva'; KIT.ajusteCena = c.ajuste || 1; KIT.tituloDesce = c.tituloDesce || 0; KIT.f = f; KIT.entMax = 0;
|
|
402
405
|
var P = c.pal; KIT.pal = P;
|
|
403
406
|
P.fonteTitulo = PL.fontes.titulo; P.fonteTexto = PL.fontes.texto; P.fonteDestaque = PL.fontes.destaque;
|
|
404
407
|
if (!CENAS[c.tipo]) KIT.avisos.push('cena ' + i + ': tipo desconhecido ' + c.tipo);
|
|
405
|
-
(CENAS[c.tipo] || cenaGancho)(comp, P, c);
|
|
408
|
+
(c.dz && CENAS_DZ[c.dz] ? CENAS_DZ[c.dz] : (CENAS[c.tipo] || cenaGancho))(comp, P, c);
|
|
406
409
|
writeLn('ENTRADA ' + JSON.stringify({ cena: i, max: Math.round(kEntradaMax(comp) * 100) / 100, f: f, dur: c.dur }));
|
|
407
410
|
var alvo = comp;
|
|
408
411
|
if (f > 1.001) {
|