morfeo 0.6.0 → 0.6.2
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/index.d.ts +2 -2
- package/lib/calidad.mjs +3 -1
- package/lib/config.mjs +1 -1
- package/lib/estructura.mjs +1 -1
- package/package.json +1 -1
- package/plantilla/gustos.md +1 -1
- package/python/musica.py +15 -1
- package/python/preparar.py +16 -1
- package/remotion/kit/ambientes.tsx +237 -13
- package/remotion/motor/Video.tsx +4 -1
- package/remotion/motor/anotaciones.tsx +1 -0
- package/remotion/motor/plantillas.tsx +25 -3
- package/remotion/motor/tema.ts +17 -3
package/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// Tipos de Morfeo: dan autocompletado en morfeo.config.mjs y videos.mjs (también en JS).
|
|
2
2
|
|
|
3
3
|
/** '#RRGGBB' o [r, g, b] */
|
|
4
|
-
export type Ambiente = 'cuarto-gamer' | 'sala' | 'arcade';
|
|
4
|
+
export type Ambiente = 'cuarto-gamer' | 'sala' | 'arcade' | 'taller' | 'oficina';
|
|
5
5
|
/** Postproceso 3D: 'cine' (foco + brillo + viñeta), 'miniatura' (tilt-shift), 'suave', false o ajustes propios. */
|
|
6
6
|
export type Post3D = 'cine' | 'miniatura' | 'suave' | false | {foco?: boolean | number; brillo?: number; vinieta?: number; miniatura?: number};
|
|
7
7
|
export type Color = string | [number, number, number];
|
|
@@ -87,7 +87,7 @@ export type Capa = CapaBase &
|
|
|
87
87
|
| {tipo: 'fuente'; texto: string; etiqueta?: string; tam?: number; color?: string; colorTexto?: string}
|
|
88
88
|
| {tipo: 'seccion'; texto: string; tam?: number; color?: string}
|
|
89
89
|
| {tipo: 'insignia'; texto: string | number; tam?: number; fondo?: string; colorTexto?: string; inclinar?: number}
|
|
90
|
-
| {tipo: 'sello'; texto: string; tam?: number; color?: string; inclinar?: number}
|
|
90
|
+
| {tipo: 'sello'; texto: string; tam?: number; color?: string; inclinar?: number; /** caja detrás (sobre fondos claros) */ fondo?: string}
|
|
91
91
|
| {tipo: 'terminal'; lineas: (string | {texto: string; en?: Momento; salida?: boolean; color?: string})[]; titulo?: string; ancho?: number; tam?: number; fondo?: string; velocidad?: number}
|
|
92
92
|
| {tipo: 'nombre'; texto: string; sub?: string; tam?: number; fondo?: string; colorTexto?: string; borde?: string}
|
|
93
93
|
| {tipo: 'accion'; texto: string; tam?: number; color?: string; fondo?: string; colorTexto?: string}
|
package/lib/calidad.mjs
CHANGED
|
@@ -149,7 +149,9 @@ export async function revisarCuadros(mp4, d) {
|
|
|
149
149
|
// medido: escenas sin nada sobre fondo oscuro → media 13–15 y la cubeta más oscura con 97 % del cuadro;
|
|
150
150
|
// tele o pared pegadas a la cámara → desviación 12–16 con media > 130; los videos buenos del canal: desviación ≥ 27
|
|
151
151
|
negro: (s) => (s.media < 16 && s.desv < 9) || (s.media < 25 && s.dom > 0.9),
|
|
152
|
-
|
|
152
|
+
// (el segundo caso pide además un color dominante: una escena de día con cielo pastel también tiene desviación
|
|
153
|
+
// baja, ~17, pero su color más repetido no pasa del 25 %)
|
|
154
|
+
tapado: (s) => (s.dom > 0.58 && s.luzDom > 45) || (s.desv < 18 && s.media > 60 && s.dom > 0.4),
|
|
153
155
|
plano: (s) => s.desv < 14 && s.media >= 25 && s.media <= 60,
|
|
154
156
|
};
|
|
155
157
|
const TEXTO = {
|
package/lib/config.mjs
CHANGED
|
@@ -28,7 +28,7 @@ export const ROTULOS = ['normal', 'contorno', 'pastilla', 'sello', 'paso', 'pixe
|
|
|
28
28
|
export const MODOS = ['rayosx', 'plano', 'holograma'];
|
|
29
29
|
export const ESTILOS = ['datos-curiosos', 'explicativo', 'documental', 'ilustrado', 'historia', 'mesa', 'serie', 'infografia', '3d', 'maqueta', 'minimal'];
|
|
30
30
|
export const TIPOS_CAPA = ['imagen', 'video', 'texto', 'contador', 'chip', 'etiqueta', 'numero', 'tarjeta', 'telefono', 'forma', 'icono', 'emoji', 'marca', 'particulas', 'grupo', 'modelo', 'componente', 'ambiente', 'barra', 'medidor', 'votacion', 'estado', 'noticia', 'chat', 'onda', 'globo', 'seguir', 'personaje', 'escena3d', 'ventana', 'escenario', 'recurso', 'dato', 'medida', 'trazo', 'contorno', 'fuente', 'seccion', 'insignia', 'sello', 'cursor', 'terminal', 'nombre', 'accion', 'reloj', 'cronometro', 'termometro', 'recipientes', 'ficha', 'checklist', 'mapa', 'bloqueo', 'llamada', 'recuento', 'capitulo', 'vigilancia', 'cintillo', 'fecha', 'calendario', 'dividida', 'tierra', 'linea'];
|
|
31
|
-
export const AMBIENTES = ['cuarto-gamer', 'sala', 'arcade'];
|
|
31
|
+
export const AMBIENTES = ['cuarto-gamer', 'sala', 'arcade', 'taller', 'oficina'];
|
|
32
32
|
export const ESCENARIOS = ['bosque', 'cuarto', 'departamento', 'tunel', 'pasillo'];
|
|
33
33
|
export const FORMAS_3D = ['moneda', 'billete', 'lingote', 'cubo', 'esfera', 'corazon', 'estrella', 'rayo', 'diamante', 'trofeo', 'cohete', 'foco', 'reloj', 'planeta', 'arbol', 'nube', 'regalo', 'llave', 'candado', 'casa', 'libro', 'globo', 'medalla', 'escudo', 'pin', 'tuberia', 'muro', 'viga', 'columna', 'escalera', 'losa', 'tanque', 'suelo'];
|
|
34
34
|
export const PROVEEDORES_VOZ = ['kokoro', 'elevenlabs', 'openai'];
|
package/lib/estructura.mjs
CHANGED
|
@@ -129,7 +129,7 @@ export function revisarEstructura(v, d) {
|
|
|
129
129
|
const capas = (s.capas ?? []).filter(Boolean);
|
|
130
130
|
if (!conMundo && (s.type ?? 'capas') === 'capas' && !capas.length && !s.rotulo) {
|
|
131
131
|
vacias++;
|
|
132
|
-
add('vacia', 'aviso', `Escena ${i + 1}: solo el subtítulo sobre el fondo. Agrega una capa o dale al video un \`ambiente: 'cuarto-gamer'\` (o 'sala', 'arcade').`);
|
|
132
|
+
add('vacia', 'aviso', `Escena ${i + 1}: solo el subtítulo sobre el fondo. Agrega una capa o dale al video un \`ambiente: 'cuarto-gamer'\` (o 'sala', 'arcade', 'taller', 'oficina').`);
|
|
133
133
|
}
|
|
134
134
|
if (capas.length && capas.every((c) => SOLO_TEXTO.has(c.tipo))) soloTexto++;
|
|
135
135
|
for (const c of capas) tipos.set(c.tipo, (tipos.get(c.tipo) ?? 0) + 1);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "morfeo",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.2",
|
|
4
4
|
"description": "Genera videos verticales animados (Remotion + Three.js) con voz, música y subtítulos, desde un guion en código o escrito por IA.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "SEE LICENSE IN LICENSE",
|
package/plantilla/gustos.md
CHANGED
|
@@ -5,7 +5,7 @@ manera: una por línea. Cuando le corrijas algo a un video y quieras que no se r
|
|
|
5
5
|
|
|
6
6
|
## Lo que sí
|
|
7
7
|
|
|
8
|
-
- Siempre un fondo vivo: un `ambiente` ('cuarto-gamer', 'sala', 'arcade') o un `mundo` propio. Nada de texto sobre un color liso.
|
|
8
|
+
- Siempre un fondo vivo: un `ambiente` ('cuarto-gamer', 'sala', 'arcade', 'taller', 'oficina') o un `mundo` propio. Nada de texto sobre un color liso.
|
|
9
9
|
- Objetos 2D/3D (`recurso`) antes que emojis.
|
|
10
10
|
- Gancho en segunda persona en los primeros 2 segundos.
|
|
11
11
|
|
package/python/musica.py
CHANGED
|
@@ -323,6 +323,18 @@ def cama(nombre, N, rng):
|
|
|
323
323
|
if nombre == "sala":
|
|
324
324
|
murmullo = lp(hp(rng.standard_normal(N), 250), 1400) * 0.035 * (0.6 + 0.4 * np.abs(np.sin(2 * np.pi * 0.9 * t + np.sin(2 * np.pi * 0.23 * t) * 3)))
|
|
325
325
|
return lluvia(0.03) + murmullo + zumbido
|
|
326
|
+
if nombre == "taller":
|
|
327
|
+
x = lluvia(0.025) + zumbido * 0.6
|
|
328
|
+
for _ in range(int(N / SR / 3)): # herramientas: clics metálicos sueltos
|
|
329
|
+
n = int(0.03 * SR); g = int(rng.random() * (N - n))
|
|
330
|
+
x[g:g + n] += 0.03 * hp(rng.standard_normal(n), 3000) * np.exp(-np.arange(n) / (0.004 * SR))
|
|
331
|
+
return x
|
|
332
|
+
if nombre == "oficina":
|
|
333
|
+
x = lp(hp(rng.standard_normal(N), 200), 900) * 0.02 + zumbido * 0.5 # aire acondicionado
|
|
334
|
+
for _ in range(int(N / SR / 1.2)): # teclas lejanas
|
|
335
|
+
n = int(0.012 * SR); g = int(rng.random() * (N - n))
|
|
336
|
+
x[g:g + n] += 0.02 * hp(rng.standard_normal(n), 2500) * np.exp(-np.arange(n) / (0.002 * SR))
|
|
337
|
+
return x
|
|
326
338
|
if nombre == "arcade":
|
|
327
339
|
x = lp(rng.standard_normal(N), 700) * 0.05 + zumbido
|
|
328
340
|
for _ in range(int(N / SR * 3)): # maquinitas sonando por todos lados
|
|
@@ -342,7 +354,7 @@ def ambiente_de(v):
|
|
|
342
354
|
return None
|
|
343
355
|
|
|
344
356
|
|
|
345
|
-
def mix(vid, style, outdir, TIM, SPEC, durs, ev, ambiente=None):
|
|
357
|
+
def mix(vid, style, outdir, TIM, SPEC, durs, ev, ambiente=None, cortes=()):
|
|
346
358
|
"""Mezcla voz + música (con ducking) + efectos y escribe mix.wav. style=None: sin música."""
|
|
347
359
|
dur = sum(durs); N = int(dur * SR); rng = np.random.default_rng(11)
|
|
348
360
|
music = musica_de(vid, style, dur, N)
|
|
@@ -375,6 +387,8 @@ def mix(vid, style, outdir, TIM, SPEC, durs, ev, ambiente=None):
|
|
|
375
387
|
if not explicito and any(abs(tc - u) < 0.25 for u in usados): continue
|
|
376
388
|
usados.append(tc)
|
|
377
389
|
add(sfx, (1.0 if explicito else 0.6) * efecto(ef, rng), tc)
|
|
390
|
+
for tc in cortes: # cortes de cámara dentro de la escena (ambientes): whoosh corto y bajito
|
|
391
|
+
add(sfx, 0.09 * whoosh(rng, 0.22), max(0.0, tc - 0.08))
|
|
378
392
|
voice, _ = sf.read(os.path.join(outdir, "voz.wav"))
|
|
379
393
|
voice = resample_poly(voice, 147, 80)
|
|
380
394
|
v = np.zeros(N); v[: min(N, len(voice))] = voice[:N]
|
package/python/preparar.py
CHANGED
|
@@ -250,7 +250,22 @@ def preparar(job):
|
|
|
250
250
|
return seg["speech"] * 0.5
|
|
251
251
|
|
|
252
252
|
durs = [seg["dur"] + (0.6 if i == len(TIM) - 1 else 0) for i, seg in enumerate(TIM)]
|
|
253
|
-
|
|
253
|
+
# cortes de cámara de los ambientes (misma regla que cortesDe() en remotion/motor/Video.tsx): un whoosh suave en cada uno
|
|
254
|
+
cortes = []
|
|
255
|
+
if musica.ambiente_de(v) or any(isinstance(c, dict) and c.get("tipo") == "ambiente" for c in (v.get("mundo") or [])):
|
|
256
|
+
for si, (seg, sp) in enumerate(zip(TIM, spec)):
|
|
257
|
+
if sp.get("cortes") is False or sp.get("type") == "cta": continue
|
|
258
|
+
if isinstance(sp.get("cortes"), list):
|
|
259
|
+
cortes += [seg["t0"] + c for c in sp["cortes"] if isinstance(c, (int, float))]; continue
|
|
260
|
+
if sp.get("plano") or seg["dur"] < 3.5: continue
|
|
261
|
+
claves = [norm(c) for c in (sp.get("claves") or []) if norm(c)]
|
|
262
|
+
hecho = False
|
|
263
|
+
for w, a0, b0 in WORDS[si]:
|
|
264
|
+
c = a0 - seg["t0"]
|
|
265
|
+
if any(norm(w).startswith(k) for k in claves) and 1.4 < c < seg["dur"] - 1.1:
|
|
266
|
+
cortes.append(a0); hecho = True; break
|
|
267
|
+
if not hecho and seg["dur"] > 7: cortes.append(seg["t0"] + seg["dur"] / 2)
|
|
268
|
+
musica.mix(vid, v.get("music"), outdir, TIM, spec, durs, ev, ambiente=musica.ambiente_de(v), cortes=cortes) # -> mix.wav (voz + música con ducking + efectos)
|
|
254
269
|
|
|
255
270
|
escenas = []
|
|
256
271
|
for si, (seg, sp) in enumerate(zip(TIM, spec)):
|
|
@@ -13,7 +13,7 @@ import {Persona3D} from './mundo3d';
|
|
|
13
13
|
import type {Expresion, Pose} from './personas';
|
|
14
14
|
import {Bloque, Lienzo3D, type Etiqueta3D, type Post3D, type Toma3D, type V3} from './tres';
|
|
15
15
|
|
|
16
|
-
export type NombreAmbiente = 'cuarto-gamer' | 'sala' | 'arcade';
|
|
16
|
+
export type NombreAmbiente = 'cuarto-gamer' | 'sala' | 'arcade' | 'taller' | 'oficina';
|
|
17
17
|
export type Reaccion = {en: number; expresion?: Expresion; pose?: Pose};
|
|
18
18
|
|
|
19
19
|
type Def = {
|
|
@@ -448,6 +448,185 @@ const SetArcade: React.FC<{t: number}> = ({t}) => (
|
|
|
448
448
|
</group>
|
|
449
449
|
);
|
|
450
450
|
|
|
451
|
+
|
|
452
|
+
/** Tablero perforado con herramientas colgadas (desarmador, pinzas, llave, martillo). */
|
|
453
|
+
const Tablero: React.FC<{pos: V3}> = ({pos}) => (
|
|
454
|
+
<group position={pos}>
|
|
455
|
+
<Bloque pos={[0, 0, 0]} tam={[2.2, 1.1, 0.04]} color="#C9A57A" />
|
|
456
|
+
{Array.from({length: 40}, (_, i) => (
|
|
457
|
+
<mesh key={i} position={[-1.0 + (i % 10) * 0.22, -0.42 + Math.floor(i / 10) * 0.28, 0.025]}>
|
|
458
|
+
<circleGeometry args={[0.012, 6]} />
|
|
459
|
+
<meshBasicMaterial color="#7A5C3A" />
|
|
460
|
+
</mesh>
|
|
461
|
+
))}
|
|
462
|
+
{[
|
|
463
|
+
[-0.8, '#E5484D', 0.5, 0.05],
|
|
464
|
+
[-0.5, '#3D7BFF', 0.42, 0.05],
|
|
465
|
+
[-0.15, '#2A2A30', 0.55, 0.08],
|
|
466
|
+
[0.2, '#F2B632', 0.38, 0.06],
|
|
467
|
+
[0.55, '#30A46C', 0.5, 0.05],
|
|
468
|
+
[0.85, '#8E929C', 0.45, 0.1],
|
|
469
|
+
].map(([x, c, l, a], i) => (
|
|
470
|
+
<group key={i} position={[x as number, 0.05, 0.05]}>
|
|
471
|
+
<Bloque pos={[0, 0.12, 0]} tam={[a as number, (l as number) * 0.45, 0.04]} color={c as string} />
|
|
472
|
+
<Bloque pos={[0, -0.15, 0]} tam={[0.03, (l as number) * 0.6, 0.03]} color="#C9CCD3" metal={0.7} rugosidad={0.3} />
|
|
473
|
+
</group>
|
|
474
|
+
))}
|
|
475
|
+
</group>
|
|
476
|
+
);
|
|
477
|
+
|
|
478
|
+
const SetTaller: React.FC<{t: number}> = ({t}) => (
|
|
479
|
+
<group>
|
|
480
|
+
<Cuarto ancho={8} fondo={6} alto={3.4} pared="#3A3F48" lateral="#33373F" piso="#4A4A4F" />
|
|
481
|
+
{/* mesa de trabajo con tapete verde */}
|
|
482
|
+
<Bloque pos={[-0.6, 0.92, -2.35]} tam={[2.8, 0.08, 1.0]} color="#8A6A48" />
|
|
483
|
+
{[-1.85, 0.65].map((x) => (
|
|
484
|
+
<Bloque key={x} pos={[x, 0.45, -2.35]} tam={[0.08, 0.9, 0.9]} color="#5A4632" />
|
|
485
|
+
))}
|
|
486
|
+
<Bloque pos={[-0.6, 0.965, -2.3]} tam={[1.6, 0.01, 0.7]} color="#2E7D5B" />
|
|
487
|
+
<Tablero pos={[-0.6, 1.9, -2.88]} />
|
|
488
|
+
{/* control abierto: carcasa, placa verde y botones sueltos */}
|
|
489
|
+
<group position={[-0.7, 0.98, -2.2]} rotation={[0, 0.3, 0]}>
|
|
490
|
+
<Bloque pos={[-0.35, 0.03, 0]} tam={[0.45, 0.05, 0.25]} color="#2A2A30" radio={0.03} />
|
|
491
|
+
<Bloque pos={[0.2, 0.02, 0.02]} tam={[0.4, 0.02, 0.22]} color="#1F7A4D" />
|
|
492
|
+
{[0, 1, 2, 3].map((k) => (
|
|
493
|
+
<Bloque key={k} pos={[0.08 + (k % 2) * 0.12, 0.04, -0.04 + Math.floor(k / 2) * 0.1]} tam={[0.05, 0.02, 0.05]} color="#D4B04A" metal={0.6} />
|
|
494
|
+
))}
|
|
495
|
+
{[0, 1, 2].map((k) => (
|
|
496
|
+
<mesh key={k} position={[0.55 + k * 0.08, 0.02, 0.12]}>
|
|
497
|
+
<cylinderGeometry args={[0.025, 0.025, 0.02, 10]} />
|
|
498
|
+
<meshStandardMaterial color={['#E5484D', '#3D7BFF', '#F2B632'][k]} />
|
|
499
|
+
</mesh>
|
|
500
|
+
))}
|
|
501
|
+
</group>
|
|
502
|
+
{/* cautín con humito y lupa con brazo */}
|
|
503
|
+
<group position={[0.25, 0.98, -2.1]} rotation={[0, -0.6, 0.15]}>
|
|
504
|
+
<Bloque pos={[0, 0.03, 0]} tam={[0.4, 0.035, 0.035]} color="#E5484D" />
|
|
505
|
+
<Bloque pos={[0.27, 0.03, 0]} tam={[0.16, 0.015, 0.015]} color="#C9CCD3" metal={0.8} />
|
|
506
|
+
</group>
|
|
507
|
+
{Array.from({length: 6}, (_, i) => {
|
|
508
|
+
const f = (t * 0.45 + i / 6) % 1;
|
|
509
|
+
return (
|
|
510
|
+
<mesh key={i} position={[0.5 + Math.sin(t + i) * 0.03, 1.02 + f * 0.6, -2.1 + Math.cos(t * 0.7 + i) * 0.03]}>
|
|
511
|
+
<sphereGeometry args={[0.02 + f * 0.04, 6, 5]} />
|
|
512
|
+
<meshBasicMaterial color="#DADDE3" transparent opacity={0.4 * (1 - f)} />
|
|
513
|
+
</mesh>
|
|
514
|
+
);
|
|
515
|
+
})}
|
|
516
|
+
<group position={[-1.6, 0.96, -2.4]}>
|
|
517
|
+
<Bloque pos={[0, 0.03, 0]} tam={[0.2, 0.06, 0.2]} color="#2A2A30" />
|
|
518
|
+
<Bloque pos={[0, 0.35, 0]} tam={[0.03, 0.6, 0.03]} color="#8E929C" metal={0.6} />
|
|
519
|
+
<mesh position={[0.15, 0.62, 0.15]} rotation={[0.9, 0, 0]}>
|
|
520
|
+
<torusGeometry args={[0.14, 0.02, 8, 20]} />
|
|
521
|
+
<meshStandardMaterial color="#2A2A30" />
|
|
522
|
+
</mesh>
|
|
523
|
+
<mesh position={[0.15, 0.62, 0.15]} rotation={[0.9, 0, 0]}>
|
|
524
|
+
<circleGeometry args={[0.13, 20]} />
|
|
525
|
+
<meshStandardMaterial color="#BFE6FF" transparent opacity={0.35} />
|
|
526
|
+
</mesh>
|
|
527
|
+
</group>
|
|
528
|
+
{/* lámpara de brazo sobre la mesa */}
|
|
529
|
+
<group position={[0.45, 0.96, -2.6]}>
|
|
530
|
+
<Bloque pos={[0, 0.5, 0]} tam={[0.04, 1.0, 0.04]} color="#2A2A30" />
|
|
531
|
+
<mesh position={[0.12, 1.0, 0.2]} rotation={[0.6, 0, 0]}>
|
|
532
|
+
<coneGeometry args={[0.16, 0.22, 16, 1, true]} />
|
|
533
|
+
<meshStandardMaterial color="#F2B632" side={THREE.DoubleSide} emissive="#F2B632" emissiveIntensity={0.3} />
|
|
534
|
+
</mesh>
|
|
535
|
+
<spotLight position={[0.12, 0.95, 0.25]} angle={0.7} penumbra={0.6} intensity={6} distance={4} decay={1.4} color="#FFE2B0" castShadow shadow-mapSize={[1024, 1024]} shadow-bias={-0.0005} />
|
|
536
|
+
</group>
|
|
537
|
+
{/* repisa con cajas etiquetadas y una consola vieja abierta */}
|
|
538
|
+
<group position={[2.4, 0, -2.5]}>
|
|
539
|
+
{[0.5, 1.2, 1.9].map((y) => (
|
|
540
|
+
<Bloque key={y} pos={[0, y, 0]} tam={[1.6, 0.05, 0.6]} color="#5A4632" />
|
|
541
|
+
))}
|
|
542
|
+
{[-1, 1].map((l) => (
|
|
543
|
+
<Bloque key={l} pos={[l * 0.78, 1.0, 0]} tam={[0.05, 2.0, 0.6]} color="#5A4632" />
|
|
544
|
+
))}
|
|
545
|
+
{[0, 1, 2, 3, 4, 5].map((k) => (
|
|
546
|
+
<Bloque key={k} pos={[-0.5 + (k % 3) * 0.5, 0.68 + Math.floor(k / 3) * 0.7, 0]} tam={[0.42, 0.3, 0.45]} color={['#C49A6C', '#B8875A', '#D2A878'][k % 3]} />
|
|
547
|
+
))}
|
|
548
|
+
<Bloque pos={[0, 2.05, 0]} tam={[0.6, 0.18, 0.4]} color="#C9CCD3" radio={0.03} />
|
|
549
|
+
</group>
|
|
550
|
+
<VentanaLluvia pos={[-3.88, 2.0, -0.4]} ancho={1.4} alto={1.2} t={t} semilla={7} />
|
|
551
|
+
<Polvo centro={[-0.2, 1.6, -2.0]} tam={[2.2, 1.6, 1.4]} t={t} />
|
|
552
|
+
<pointLight position={[0, 3.1, 0]} intensity={2.2} distance={9} decay={1.6} color="#FFE9C8" />
|
|
553
|
+
</group>
|
|
554
|
+
);
|
|
555
|
+
|
|
556
|
+
const SetOficina: React.FC<{t: number}> = ({t}) => (
|
|
557
|
+
<group>
|
|
558
|
+
<Cuarto ancho={9} fondo={7} alto={3.4} pared="#D8DCE3" lateral="#C9CED8" piso="#7C8696" />
|
|
559
|
+
{/* ventanal de atardecer con la ciudad */}
|
|
560
|
+
<group position={[0, 1.9, -3.38]}>
|
|
561
|
+
<mesh>
|
|
562
|
+
<planeGeometry args={[6.4, 2.0]} />
|
|
563
|
+
<meshStandardMaterial color="#F4A26B" emissive="#F4A26B" emissiveIntensity={0.55} />
|
|
564
|
+
</mesh>
|
|
565
|
+
{Array.from({length: 12}, (_, i) => {
|
|
566
|
+
const h = 0.4 + azarN(i * 2.7) * 1.2;
|
|
567
|
+
return <Bloque key={i} pos={[-3.0 + i * 0.55, -1.0 + h / 2, 0.02]} tam={[0.45, h, 0.01]} color="#5A4A6A" />;
|
|
568
|
+
})}
|
|
569
|
+
{[-2.1, 0, 2.1].map((x) => (
|
|
570
|
+
<Bloque key={x} pos={[x + 1.05, 0, 0.04]} tam={[0.06, 2.0, 0.04]} color="#2A2A30" />
|
|
571
|
+
))}
|
|
572
|
+
</group>
|
|
573
|
+
{/* dos escritorios con monitor; en el de enfrente trabaja la persona */}
|
|
574
|
+
{[
|
|
575
|
+
[-1.6, -1.6],
|
|
576
|
+
[1.7, -1.6],
|
|
577
|
+
].map(([x, z], i) => (
|
|
578
|
+
<group key={i} position={[x, 0, z]}>
|
|
579
|
+
<Bloque pos={[0, 0.74, 0]} tam={[1.8, 0.06, 0.8]} color="#EDEDED" />
|
|
580
|
+
{[-1, 1].map((l) => (
|
|
581
|
+
<Bloque key={l} pos={[l * 0.85, 0.37, 0]} tam={[0.05, 0.74, 0.75]} color="#8E929C" metal={0.5} />
|
|
582
|
+
))}
|
|
583
|
+
<Bloque pos={[0, 0.84, -0.25]} tam={[0.25, 0.14, 0.15]} color="#2A2A30" />
|
|
584
|
+
<Tele3D pos={[0, 1.15, -0.28]} ancho={0.9} t={t} fase={i * 2.1} luz={1.2} />
|
|
585
|
+
<Bloque pos={[0.1, 0.78, 0.15]} tam={[0.5, 0.02, 0.16]} color="#2A2A30" />
|
|
586
|
+
<mesh position={[0.6, 0.82, 0.05]}>
|
|
587
|
+
<cylinderGeometry args={[0.05, 0.045, 0.1, 12]} />
|
|
588
|
+
<meshStandardMaterial color={i ? '#E5484D' : '#3D7BFF'} />
|
|
589
|
+
</mesh>
|
|
590
|
+
<SillaGamer pos={[0, 0, 0.75]} color="#2F6FE0" />
|
|
591
|
+
</group>
|
|
592
|
+
))}
|
|
593
|
+
{/* pizarrón con gráfica, garrafón de agua y plantas */}
|
|
594
|
+
<group position={[-4.38, 1.7, 0.4]} rotation={[0, Math.PI / 2, 0]}>
|
|
595
|
+
<Bloque pos={[0, 0, 0]} tam={[2.0, 1.1, 0.04]} color="#FFFFFF" />
|
|
596
|
+
{[0, 1, 2, 3].map((k) => (
|
|
597
|
+
<Bloque key={k} pos={[-0.6 + k * 0.4, -0.35 + (0.15 + k * 0.12) / 2, 0.03]} tam={[0.22, 0.15 + k * 0.12, 0.01]} color={['#3D7BFF', '#30A46C', '#F2B632', '#E5484D'][k]} />
|
|
598
|
+
))}
|
|
599
|
+
</group>
|
|
600
|
+
<group position={[3.9, 0, 0.6]}>
|
|
601
|
+
<Bloque pos={[0, 0.5, 0]} tam={[0.4, 1.0, 0.4]} color="#EDEDED" />
|
|
602
|
+
<mesh position={[0, 1.25, 0]}>
|
|
603
|
+
<cylinderGeometry args={[0.17, 0.17, 0.5, 14]} />
|
|
604
|
+
<meshStandardMaterial color="#7FC4FF" transparent opacity={0.7} />
|
|
605
|
+
</mesh>
|
|
606
|
+
</group>
|
|
607
|
+
{[-3.9, 3.9].map((x) => (
|
|
608
|
+
<group key={x} position={[x, 0, -2.9]}>
|
|
609
|
+
<Bloque pos={[0, 0.25, 0]} tam={[0.45, 0.5, 0.45]} color="#C47A4A" />
|
|
610
|
+
{[0, 1, 2, 3, 4].map((k) => (
|
|
611
|
+
<mesh key={k} position={[Math.cos(k * 1.3) * 0.12, 0.8 + k * 0.1, Math.sin(k * 1.3) * 0.12]} rotation={[0.3 * Math.cos(k), k, 0.5 + Math.sin(t * 0.8 + k) * 0.04]}>
|
|
612
|
+
<coneGeometry args={[0.16, 0.7, 4]} />
|
|
613
|
+
<meshStandardMaterial color="#3E9B5A" flatShading />
|
|
614
|
+
</mesh>
|
|
615
|
+
))}
|
|
616
|
+
</group>
|
|
617
|
+
))}
|
|
618
|
+
{/* lámparas de techo */}
|
|
619
|
+
{[-2, 2].map((x) => (
|
|
620
|
+
<group key={x} position={[x, 3.32, -0.6]}>
|
|
621
|
+
<Emisivo pos={[0, 0, 0]} tam={[1.4, 0.04, 0.3]} color="#FFFFFF" fuerza={0.9} />
|
|
622
|
+
<pointLight position={[0, -0.3, 0]} intensity={3} distance={8} decay={1.5} color="#FFF4E0" castShadow={x < 0} shadow-mapSize={[1024, 1024]} shadow-bias={-0.0005} />
|
|
623
|
+
</group>
|
|
624
|
+
))}
|
|
625
|
+
<directionalLight position={[0, 2.5, -6]} intensity={0.8} color="#FFB27A" />
|
|
626
|
+
<Polvo centro={[0, 1.8, -1.6]} tam={[5, 2.2, 2]} t={t} />
|
|
627
|
+
</group>
|
|
628
|
+
);
|
|
629
|
+
|
|
451
630
|
const AMBIENTES: Record<NombreAmbiente, Def> = {
|
|
452
631
|
'cuarto-gamer': {
|
|
453
632
|
luz: 'noche',
|
|
@@ -456,17 +635,17 @@ const AMBIENTES: Record<NombreAmbiente, Def> = {
|
|
|
456
635
|
post: 'cine',
|
|
457
636
|
planos: {
|
|
458
637
|
general: {pos: [2.6, 2.6, 3.4], mirar: [-0.7, 1.15, -1.6], fov: 50, a: {pos: [2.1, 2.4, 2.8]}},
|
|
459
|
-
persona: {pos: [
|
|
638
|
+
persona: {pos: [1.1, 1.6, -2.35], mirar: [-1.0, 1.15, -0.95], fov: 46, a: {pos: [0.95, 1.56, -2.25]}},
|
|
460
639
|
escritorio: {pos: [0.2, 1.8, 0.5], mirar: [-1.0, 1.25, -2.5], fov: 42, a: {pos: [0.05, 1.7, 0.2]}},
|
|
461
|
-
tele: {pos: [
|
|
640
|
+
tele: {pos: [-2.1, 1.9, 1.7], mirar: [-1.0, 1.3, -2.72], fov: 44, a: {pos: [-2.0, 1.85, 1.3]}},
|
|
462
641
|
consola: {pos: [1.0, 1.9, -0.5], mirar: [-0.5, 0.95, -2.35], fov: 42, a: {pos: [0.85, 1.82, -0.75]}},
|
|
463
642
|
estante: {pos: [-1.9, 1.75, 0.8], mirar: [-3.8, 1.55, -1.2], fov: 42, a: {pos: [-2.1, 1.7, 0.5]}},
|
|
464
643
|
ventana: {pos: [0.9, 1.75, 0.8], mirar: [2.3, 1.9, -2.88], fov: 44, a: {pos: [1.05, 1.75, 0.5]}},
|
|
465
644
|
contrapicado: {pos: [0.9, 0.45, -2.2], mirar: [-1.0, 1.35, -0.95], fov: 50},
|
|
466
645
|
cenital: {pos: [-0.9, 3.4, 0.4], mirar: [-1.0, 0.7, -1.5], fov: 52, a: {pos: [-0.9, 3.3, 0.1]}},
|
|
467
646
|
},
|
|
468
|
-
orden: ['general', 'persona', 'escritorio', 'tele', '
|
|
469
|
-
ordenFondo: ['general', 'escritorio', 'estante', 'ventana', '
|
|
647
|
+
orden: ['general', 'persona', 'escritorio', 'tele', 'consola', 'estante', 'ventana'],
|
|
648
|
+
ordenFondo: ['general', 'escritorio', 'estante', 'ventana', 'consola'],
|
|
470
649
|
anclas: {tele: [-1.0, 1.42, -2.72], consola: [-0.45, 0.92, -2.35], control: [0.1, 0.86, -2.1], escritorio: [-1.0, 0.8, -2.4], persona: [-1.0, 1.3, -0.95], estante: [-3.8, 1.5, -1.2], ventana: [2.3, 2.1, -2.88], cama: [2.75, 0.6, -1.6], silla: [-1.0, 0.9, -0.95]},
|
|
471
650
|
persona: {pos: [-1.0, 0, -0.98], rot: [0, 180, 0], pose: 'sentado'},
|
|
472
651
|
Set: SetCuartoGamer,
|
|
@@ -481,7 +660,7 @@ const AMBIENTES: Record<NombreAmbiente, Def> = {
|
|
|
481
660
|
persona: {pos: [0.5, 1.3, -1.6], mirar: [0, 1.15, 0.9], fov: 44, a: {pos: [0.4, 1.28, -1.4]}},
|
|
482
661
|
tele: {pos: [0.7, 1.6, -0.3], mirar: [0, 1.15, -3.2], fov: 42, a: {pos: [0.6, 1.55, -0.6]}},
|
|
483
662
|
sillon: {pos: [2.6, 1.6, -0.6], mirar: [0, 0.8, 0.9], fov: 42, a: {pos: [2.3, 1.5, -0.4]}},
|
|
484
|
-
gato: {pos: [2.
|
|
663
|
+
gato: {pos: [2.7, 2.0, -0.9], mirar: [1.1, 0.85, 0.9], fov: 44, a: {pos: [2.55, 1.95, -0.75]}},
|
|
485
664
|
espalda: {pos: [0.6, 1.9, 3.0], mirar: [0, 1.1, -2.6], fov: 44, a: {pos: [0.5, 1.8, 2.6]}},
|
|
486
665
|
lampara: {pos: [-1.6, 1.6, 0.4], mirar: [-3.4, 1.4, -2.4], fov: 44},
|
|
487
666
|
ventana: {pos: [-1.8, 1.7, 1.6], mirar: [-4.38, 1.9, -0.6], fov: 44, a: {pos: [-2.0, 1.7, 1.3]}},
|
|
@@ -500,18 +679,58 @@ const AMBIENTES: Record<NombreAmbiente, Def> = {
|
|
|
500
679
|
planos: {
|
|
501
680
|
general: {pos: [3.2, 2.6, 3.6], mirar: [-0.4, 1.3, -2.2], fov: 50, a: {pos: [2.6, 2.4, 3.0]}},
|
|
502
681
|
persona: {pos: [1.7, 1.6, -2.7], mirar: [0.6, 1.4, -0.95], fov: 44, a: {pos: [1.6, 1.58, -2.55]}},
|
|
503
|
-
maquina: {pos: [1.
|
|
682
|
+
maquina: {pos: [1.7, 1.95, -0.5], mirar: [0.4, 1.3, -3.3], fov: 44, a: {pos: [1.6, 1.9, -0.75]}},
|
|
504
683
|
pasillo: {pos: [-3.6, 1.6, 2.6], mirar: [-4.0, 1.2, -2.2], fov: 46, a: {pos: [-3.5, 1.6, 2.1]}},
|
|
505
684
|
neon: {pos: [1.4, 2.4, -1.2], mirar: [2.6, 2.9, -3.88], fov: 42},
|
|
506
685
|
contrapicado: {pos: [1.8, 0.5, 0.4], mirar: [0.4, 1.3, -3.0], fov: 52},
|
|
507
686
|
espalda: {pos: [2.3, 1.9, 1.3], mirar: [-0.2, 1.35, -3.4], fov: 46, a: {pos: [2.15, 1.85, 1.0]}},
|
|
508
687
|
},
|
|
509
|
-
orden: ['general', 'persona', 'maquina', 'espalda', 'pasillo'
|
|
510
|
-
|
|
688
|
+
orden: ['general', 'persona', 'maquina', 'espalda', 'pasillo'],
|
|
689
|
+
// general y espalda dejan a la persona al centro, justo donde caen las capas 2D
|
|
690
|
+
ordenFondo: ['pasillo', 'maquina'],
|
|
511
691
|
anclas: {maquina: [0.6, 1.42, -3.0], persona: [0.6, 1.6, -0.9], neon: [2.6, 2.9, -3.88], pasillo: [-4.4, 1.4, -0.6]},
|
|
512
692
|
persona: {pos: [0.6, 0, -0.95], rot: [0, 180, 0], pose: 'parado'},
|
|
513
693
|
Set: SetArcade,
|
|
514
694
|
},
|
|
695
|
+
taller: {
|
|
696
|
+
luz: 'noche',
|
|
697
|
+
fondo: '#0E0F14',
|
|
698
|
+
niebla: [10, 24],
|
|
699
|
+
post: 'cine',
|
|
700
|
+
planos: {
|
|
701
|
+
general: {pos: [2.4, 2.4, 3.2], mirar: [-0.4, 1.2, -2.0], fov: 50, a: {pos: [2.0, 2.2, 2.6]}},
|
|
702
|
+
mesa: {pos: [0.3, 1.9, -0.6], mirar: [-0.6, 1.0, -2.3], fov: 42, a: {pos: [0.2, 1.8, -0.85]}},
|
|
703
|
+
control: {pos: [0.2, 2.1, -0.6], mirar: [-0.7, 1.0, -2.2], fov: 44, a: {pos: [0.1, 2.0, -0.8]}},
|
|
704
|
+
persona: {pos: [1.4, 1.7, -2.1], mirar: [-1.2, 1.3, -1.4], fov: 46, a: {pos: [1.25, 1.66, -2.0]}},
|
|
705
|
+
tablero: {pos: [0.6, 1.9, 0.4], mirar: [-0.6, 1.8, -2.88], fov: 44, a: {pos: [0.5, 1.88, 0.1]}},
|
|
706
|
+
repisa: {pos: [0.5, 1.6, 0.6], mirar: [2.4, 1.3, -2.5], fov: 44, a: {pos: [0.6, 1.6, 0.3]}},
|
|
707
|
+
cenital: {pos: [-0.6, 3.2, -0.9], mirar: [-0.6, 0.95, -2.2], fov: 50},
|
|
708
|
+
},
|
|
709
|
+
orden: ['general', 'persona', 'mesa', 'control', 'tablero', 'repisa', 'cenital'],
|
|
710
|
+
ordenFondo: ['general', 'mesa', 'tablero', 'repisa', 'cenital'],
|
|
711
|
+
anclas: {mesa: [-0.6, 1.0, -2.35], control: [-0.7, 1.02, -2.2], cautin: [0.25, 1.02, -2.1], lupa: [-1.45, 1.6, -2.25], tablero: [-0.6, 1.9, -2.88], repisa: [2.4, 1.3, -2.5], persona: [-1.2, 1.65, -1.4]},
|
|
712
|
+
persona: {pos: [-1.2, 0, -1.45], rot: [0, 160, 0], pose: 'parado'},
|
|
713
|
+
Set: SetTaller,
|
|
714
|
+
},
|
|
715
|
+
oficina: {
|
|
716
|
+
luz: 'noche',
|
|
717
|
+
fondo: '#1A1620',
|
|
718
|
+
niebla: [12, 30],
|
|
719
|
+
post: 'cine',
|
|
720
|
+
planos: {
|
|
721
|
+
general: {pos: [3.0, 2.6, 3.8], mirar: [-0.3, 1.1, -1.6], fov: 50, a: {pos: [2.5, 2.4, 3.2]}},
|
|
722
|
+
persona: {pos: [0.0, 1.6, -2.2], mirar: [-1.6, 1.22, -0.85], fov: 44, a: {pos: [-0.1, 1.58, -2.05]}},
|
|
723
|
+
monitor: {pos: [-0.2, 2.1, 0.3], mirar: [-1.6, 1.1, -1.88], fov: 42, a: {pos: [-0.3, 2.0, 0.05]}},
|
|
724
|
+
ventana: {pos: [0.6, 1.6, 1.6], mirar: [0, 1.9, -3.38], fov: 46, a: {pos: [0.5, 1.6, 1.2]}},
|
|
725
|
+
pizarron: {pos: [-1.2, 1.7, 1.6], mirar: [-4.38, 1.6, 0.4], fov: 44},
|
|
726
|
+
companero: {pos: [2.9, 1.7, 0.6], mirar: [1.7, 1.0, -1.7], fov: 44, a: {pos: [2.8, 1.65, 0.35]}},
|
|
727
|
+
},
|
|
728
|
+
orden: ['general', 'persona', 'monitor', 'ventana', 'pizarron', 'companero'],
|
|
729
|
+
ordenFondo: ['general', 'monitor', 'ventana', 'pizarron', 'companero'],
|
|
730
|
+
anclas: {monitor: [-1.6, 1.15, -1.88], escritorio: [-1.6, 0.8, -1.6], ventana: [0, 1.9, -3.38], pizarron: [-4.38, 1.7, 0.4], garrafon: [3.9, 1.25, 0.6], persona: [-1.6, 1.3, -0.85]},
|
|
731
|
+
persona: {pos: [-1.6, 0, -0.88], rot: [0, 180, 0], pose: 'sentado'},
|
|
732
|
+
Set: SetOficina,
|
|
733
|
+
},
|
|
515
734
|
};
|
|
516
735
|
|
|
517
736
|
export const NOMBRES_AMBIENTES = Object.keys(AMBIENTES) as NombreAmbiente[];
|
|
@@ -587,14 +806,19 @@ export type PropsAmbiente = {
|
|
|
587
806
|
function tramos(nombre: NombreAmbiente, base: Toma3D[], inicios: number[], cortes: number[][], conCapas: boolean[]) {
|
|
588
807
|
const def = AMBIENTES[nombre];
|
|
589
808
|
const out: {ini: number; toma: Toma3D; escena: number}[] = [];
|
|
590
|
-
|
|
809
|
+
const igual = (a?: Toma3D, b?: Toma3D) => !!a && !!b && a.pos === b.pos && a.mirar === b.mirar;
|
|
810
|
+
base.forEach((toma0, i) => {
|
|
591
811
|
const ini = inicios[i] ?? (out.length ? out[out.length - 1].ini + 3 : 0);
|
|
592
|
-
out.push({ini, toma, escena: i + 1});
|
|
593
812
|
const lista = (conCapas[i] ? def.ordenFondo : def.orden).map((n) => def.planos[n]);
|
|
594
|
-
|
|
813
|
+
// si el último tramo (un corte de la escena anterior) ya usó este plano, la escena toma el siguiente
|
|
814
|
+
let toma = toma0;
|
|
815
|
+
if (igual(toma, out[out.length - 1]?.toma)) toma = lista[(Math.max(0, lista.findIndex((x) => igual(x, toma))) + 1) % lista.length];
|
|
816
|
+
// entre escenas, corte seco: el viaje de cámara cruzaba paredes y muebles del set
|
|
817
|
+
out.push({ini, toma: i > 0 ? {...toma, corte: true} : toma, escena: i + 1});
|
|
818
|
+
let j = Math.max(0, lista.findIndex((x) => igual(x, toma)));
|
|
595
819
|
for (const c of cortes[i] ?? []) {
|
|
596
820
|
j = (j + 2) % lista.length;
|
|
597
|
-
if (lista[j]
|
|
821
|
+
if (igual(lista[j], toma) || igual(lista[j], base[i + 1])) j = (j + 1) % lista.length;
|
|
598
822
|
out.push({ini: ini + c, toma: {...lista[j], corte: true}, escena: i + 1});
|
|
599
823
|
}
|
|
600
824
|
});
|
package/remotion/motor/Video.tsx
CHANGED
|
@@ -413,7 +413,8 @@ const MarcaEsquina: React.FC<{tema: Tema}> = ({tema}) => {
|
|
|
413
413
|
};
|
|
414
414
|
|
|
415
415
|
export const VideoMotor: React.FC<{d: Datos; T: number; estilos?: Record<string, any>; miniatura?: boolean; sinMarca?: boolean}> = ({d, T, estilos, miniatura, sinMarca}) => {
|
|
416
|
-
const
|
|
416
|
+
const conAmbiente = !!d.mundo?.some((c: any) => c.tipo === 'ambiente');
|
|
417
|
+
const tema = temaDe(d.estilo, d.tema, estilos, conAmbiente);
|
|
417
418
|
const n = d.escenas.length;
|
|
418
419
|
let si = d.escenas.findIndex((e) => T < e.t0 + e.dur);
|
|
419
420
|
if (si < 0) si = n - 1;
|
|
@@ -473,6 +474,8 @@ export const VideoMotor: React.FC<{d: Datos; T: number; estilos?: Record<string,
|
|
|
473
474
|
<Mundo d={d} si={si} t={t} T={T} tema={tema} />
|
|
474
475
|
</AbsoluteFill>
|
|
475
476
|
) : null}
|
|
477
|
+
{/* un ambiente puede ser claro (oficina): sombra arriba, donde van subtítulos y titular, para que el texto blanco se lea (en el cierre, todo el cuadro) */}
|
|
478
|
+
{conAmbiente ? <AbsoluteFill style={{background: e.tipo === 'cta' ? 'rgba(0,0,0,0.42)' : 'linear-gradient(180deg, rgba(0,0,0,0.5) 0%, rgba(0,0,0,0.38) 30%, rgba(0,0,0,0) 46%)'}} /> : null}
|
|
476
479
|
{enTransicion && prev ? (
|
|
477
480
|
<AbsoluteFill style={estiloPrevio}>
|
|
478
481
|
<EscenaVista e={prev} t={prev.dur + t} tema={tema} semilla={semilla} sinFondo={conMundo} />
|
|
@@ -204,6 +204,7 @@ export const Anotacion: React.FC<{c: Capa; ctx: CtxEscena}> = ({c, ctx}) => {
|
|
|
204
204
|
border: `${Math.max(4, tam * 0.1)}px solid ${color}`,
|
|
205
205
|
outline: `${Math.max(2, tam * 0.04)}px solid ${color}`,
|
|
206
206
|
outlineOffset: tam * 0.1,
|
|
207
|
+
background: c.fondo,
|
|
207
208
|
borderRadius: tam * 0.2,
|
|
208
209
|
padding: `${tam * 0.12}px ${tam * 0.42}px`,
|
|
209
210
|
fontFamily: T.fuentes.titulo,
|
|
@@ -11,6 +11,22 @@
|
|
|
11
11
|
import type {Marca} from '../tipos';
|
|
12
12
|
import type {Capa} from './capas';
|
|
13
13
|
import type {Tema} from './tema';
|
|
14
|
+
import {ancho} from '../estilo';
|
|
15
|
+
|
|
16
|
+
/** Renglones que ocupa un texto al partirse por palabras en `maxAncho` (como lo hace el navegador). */
|
|
17
|
+
const renglonesDe = (texto: string, fuente: string, tam: number, maxAncho: number, mayus?: boolean) => {
|
|
18
|
+
const palabras = (mayus ? texto.toUpperCase() : texto).split(/\s+/).filter(Boolean);
|
|
19
|
+
let n = 1;
|
|
20
|
+
let linea = '';
|
|
21
|
+
for (const w of palabras) {
|
|
22
|
+
const prueba = linea ? `${linea} ${w}` : w;
|
|
23
|
+
if (linea && ancho(prueba, fuente, tam, 800) > maxAncho) {
|
|
24
|
+
n++;
|
|
25
|
+
linea = w;
|
|
26
|
+
} else linea = prueba;
|
|
27
|
+
}
|
|
28
|
+
return n;
|
|
29
|
+
};
|
|
14
30
|
|
|
15
31
|
type P = Record<string, any>;
|
|
16
32
|
|
|
@@ -22,10 +38,16 @@ export function capasDe(tipo: string, p: P, tema: Tema, marca: Marca): Capa[] {
|
|
|
22
38
|
case 'titular': {
|
|
23
39
|
const y = p.y ?? 560;
|
|
24
40
|
const out: Capa[] = [];
|
|
25
|
-
|
|
26
|
-
|
|
41
|
+
const tam = p.tam ?? 104;
|
|
42
|
+
const maxAncho = p.numero != null ? 800 : 920;
|
|
43
|
+
// el texto va centrado en `y`: si se parte en 2+ renglones crece hacia arriba y abajo, así que la etiqueta
|
|
44
|
+
// y el subtítulo se separan medio renglón por cada renglón extra (ajuste por palabras con la fuente real)
|
|
45
|
+
const renglones = renglonesDe(String(p.texto ?? '').replace(/\*/g, ''), tema.fuentes.titulo, tam, maxAncho, tema.tituloMayusculas);
|
|
46
|
+
const extraY = (renglones - 1) * tam * 0.525;
|
|
47
|
+
if (p.etiqueta) out.push({tipo: 'etiqueta', texto: p.etiqueta, x: 80, y: y - 92 - extraY, ancla: 'izquierda', entrada: 'aparecer'});
|
|
48
|
+
out.push({tipo: 'texto', texto: p.texto ?? '', x: 80, y, ancla: 'izquierda', alinear: 'left', tam, maxAncho, entrada: 'subir', en: p.en});
|
|
27
49
|
if (p.numero != null) out.push({tipo: 'insignia', texto: String(p.numero), x: 1000, y, ancla: 'derecha', entrada: 'rebote', en: 0.15});
|
|
28
|
-
if (p.sub) out.push({tipo: 'texto', texto: p.sub, fuente: 'texto', tam: 44, peso: 600, color: tema.colores.suave, x: 80, y: y + 110, ancla: 'izquierda', alinear: 'left', entrada: 'aparecer', en: 0.3});
|
|
50
|
+
if (p.sub) out.push({tipo: 'texto', texto: p.sub, fuente: 'texto', tam: 44, peso: 600, color: tema.colores.suave, x: 80, y: y + 110 + extraY, ancla: 'izquierda', alinear: 'left', entrada: 'aparecer', en: 0.3});
|
|
29
51
|
return [...out, ...extra];
|
|
30
52
|
}
|
|
31
53
|
case 'dato': {
|
package/remotion/motor/tema.ts
CHANGED
|
@@ -252,13 +252,27 @@ export const mezclar = <T,>(a: T, b: any): T => {
|
|
|
252
252
|
return out;
|
|
253
253
|
};
|
|
254
254
|
|
|
255
|
-
/**
|
|
256
|
-
|
|
255
|
+
/**
|
|
256
|
+
* Estilos de papel o cielo (texto oscuro) sobre un ambiente 3D de noche: el texto pasa a claro para que se lea.
|
|
257
|
+
* Los ajustes `tema` del video se aplican después, así que siguen mandando.
|
|
258
|
+
*/
|
|
259
|
+
const SOBRE_NOCHE = {
|
|
260
|
+
textura: 'ninguna',
|
|
261
|
+
colores: {texto: '#FFFFFF', suave: 'rgba(255,255,255,0.6)', tarjeta: '#1C1D24', tarjetaTexto: '#FFFFFF', borde: '#FFFFFF'},
|
|
262
|
+
subtitulos: {color: '#FFFFFF', pendiente: 'rgba(255,255,255,0.5)'},
|
|
263
|
+
};
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Tema final: estilo base (o uno propio del config, que puede heredar con `base`) + ajustes del video.
|
|
267
|
+
* `oscuro`: el video corre sobre un ambiente de noche (capa `ambiente` en el mundo).
|
|
268
|
+
*/
|
|
269
|
+
export function temaDe(estilo: string | undefined, ajustes: Record<string, any> | undefined, propios: Record<string, any> = {}, oscuro = false): Tema {
|
|
257
270
|
const resolver = (nombre: string, n = 0): Tema => {
|
|
258
271
|
if (ESTILOS[nombre]) return ESTILOS[nombre];
|
|
259
272
|
const p = propios[nombre];
|
|
260
273
|
if (p && n < 5) return mezclar(resolver(p.base ?? 'minimal', n + 1), p);
|
|
261
274
|
return ESTILOS.minimal;
|
|
262
275
|
};
|
|
263
|
-
|
|
276
|
+
const base = resolver(estilo ?? 'minimal');
|
|
277
|
+
return mezclar(oscuro && base.colores.texto !== '#FFFFFF' ? mezclar(base, SOBRE_NOCHE) : base, ajustes ?? {});
|
|
264
278
|
}
|