morfeo 0.6.1 → 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 CHANGED
@@ -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
- tapado: (s) => (s.dom > 0.58 && s.luzDom > 45) || (s.desv < 18 && s.media > 60),
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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "morfeo",
3
- "version": "0.6.1",
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",
@@ -686,7 +686,8 @@ const AMBIENTES: Record<NombreAmbiente, Def> = {
686
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]}},
687
687
  },
688
688
  orden: ['general', 'persona', 'maquina', 'espalda', 'pasillo'],
689
- ordenFondo: ['general', 'espalda', 'pasillo', 'maquina'],
689
+ // general y espalda dejan a la persona al centro, justo donde caen las capas 2D
690
+ ordenFondo: ['pasillo', 'maquina'],
690
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]},
691
692
  persona: {pos: [0.6, 0, -0.95], rot: [0, 180, 0], pose: 'parado'},
692
693
  Set: SetArcade,
@@ -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 tema = temaDe(d.estilo, d.tema, estilos);
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
- if (p.etiqueta) out.push({tipo: 'etiqueta', texto: p.etiqueta, x: 80, y: y - 92, ancla: 'izquierda', entrada: 'aparecer'});
26
- out.push({tipo: 'texto', texto: p.texto ?? '', x: 80, y, ancla: 'izquierda', alinear: 'left', tam: p.tam ?? 104, maxAncho: p.numero != null ? 800 : 920, entrada: 'subir', en: p.en});
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': {
@@ -252,13 +252,27 @@ export const mezclar = <T,>(a: T, b: any): T => {
252
252
  return out;
253
253
  };
254
254
 
255
- /** Tema final: estilo base (o uno propio del config, que puede heredar con `base`) + ajustes del video. */
256
- export function temaDe(estilo: string | undefined, ajustes: Record<string, any> | undefined, propios: Record<string, any> = {}): Tema {
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
- return mezclar(resolver(estilo ?? 'minimal'), ajustes ?? {});
276
+ const base = resolver(estilo ?? 'minimal');
277
+ return mezclar(oscuro && base.colores.texto !== '#FFFFFF' ? mezclar(base, SOBRE_NOCHE) : base, ajustes ?? {});
264
278
  }