morfeo 0.1.1 → 0.1.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/README.md +1 -0
- package/bin/morfeo.mjs +26 -1
- package/index.d.ts +13 -4
- package/lib/config.mjs +38 -3
- package/lib/diccionario.mjs +129 -0
- package/lib/guion.mjs +25 -7
- package/lib/recursos.mjs +299 -0
- package/lib/render.mjs +29 -3
- package/package.json +1 -1
- package/plantilla/videos.mjs +1 -0
- package/remotion/motor/Video.tsx +1 -1
- package/remotion/motor/capas.tsx +10 -0
- package/remotion/motor/escenarios/Bosque.tsx +157 -0
- package/remotion/motor/escenarios/Cuarto.tsx +103 -0
- package/remotion/motor/escenarios/Noche.tsx +163 -0
- package/remotion/motor/escenarios/index.tsx +26 -0
- package/remotion/motor/fondo.tsx +16 -4
- package/remotion/motor/modelo3d.tsx +4 -1
- package/remotion/motor/objetos3d.tsx +548 -0
- package/remotion/motor/plantillas.tsx +7 -3
- package/remotion/motor/tema.ts +5 -4
- package/remotion/motor/widgets.tsx +1 -1
package/README.md
CHANGED
|
@@ -21,6 +21,7 @@ la voz, la música, los efectos de sonido, los subtítulos, las animaciones y la
|
|
|
21
21
|
|
|
22
22
|
- **Voces:** Kokoro (gratis, en tu computadora, más de 50 voces y mezclas), ElevenLabs, OpenAI, tu propia
|
|
23
23
|
grabación por escena o cualquier servicio que conectes con una función.
|
|
24
|
+
- **Objetos 2D y 3D:** una biblioteca con diccionario temático en español (`{tipo: 'recurso', buscar: 'tubería', estilo: '3d'}`) que busca en Kenney, Poly Haven e Iconify (miles de modelos e ilustraciones libres), más objetos 3D y piezas de construcción hechos en código (tuberías con agua, muros, vigas…) y escenarios ilustrados animados. Los emojis quedan como último recurso.
|
|
24
25
|
- **Música:** 7 estilos generados sin derechos de autor, o tu propia pista (mp3, wav, flac, ogg). Baja sola cuando habla la voz.
|
|
25
26
|
- **Guiones:** los escribes tú, o Claude escribe el video completo a partir de un tema o de tu texto.
|
|
26
27
|
- **Subtítulos:** palabra por palabra; con Whisper quedan sincronizados con la voz real.
|
package/bin/morfeo.mjs
CHANGED
|
@@ -22,6 +22,8 @@ Videos
|
|
|
22
22
|
--gpu usa la tarjeta de video (más rápido; si falla, quítalo)
|
|
23
23
|
studio Abre el editor en el navegador para ver y ajustar en vivo
|
|
24
24
|
estilos Lista los estilos visuales disponibles
|
|
25
|
+
recursos temas Diccionario de objetos 2D/3D por temática
|
|
26
|
+
recursos buscar "x" Busca un objeto (--3d, --realista, --2d --variante clay)
|
|
25
27
|
|
|
26
28
|
Guiones
|
|
27
29
|
guion "tema" La IA (Claude) escribe un video completo sobre el tema
|
|
@@ -46,7 +48,7 @@ function opciones(argv) {
|
|
|
46
48
|
const [k, v] = a.slice(2).split('=');
|
|
47
49
|
const clave = k.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
|
|
48
50
|
const sig = argv[i + 1];
|
|
49
|
-
const conValor = ['proyecto', 'desde', 'id', 'voz', 'duracion', 'idioma', 'estilo', 'tono', 'musica', 'titulo', 'modelo', 'puerto'];
|
|
51
|
+
const conValor = ['proyecto', 'desde', 'id', 'voz', 'duracion', 'idioma', 'estilo', 'tono', 'variante', 'fuente', 'musica', 'titulo', 'modelo', 'puerto'];
|
|
50
52
|
if (v != null) op[clave] = v;
|
|
51
53
|
else if (conValor.includes(clave) && sig != null && !sig.startsWith('--')) op[clave] = argv[++i];
|
|
52
54
|
else op[clave] = true;
|
|
@@ -97,6 +99,29 @@ async function main() {
|
|
|
97
99
|
const {studio} = await import('../lib/render.mjs');
|
|
98
100
|
return studio({proyecto, puerto: op.puerto});
|
|
99
101
|
}
|
|
102
|
+
case 'recursos':
|
|
103
|
+
case 'recurso': {
|
|
104
|
+
const [sub, ...resto] = pos;
|
|
105
|
+
const {buscarRecursos, VARIANTES_2D} = await import('../lib/recursos.mjs');
|
|
106
|
+
const {listaTemas, traducir} = await import('../lib/diccionario.mjs');
|
|
107
|
+
if (sub === 'temas' || !sub) {
|
|
108
|
+
for (const [t, terminos] of Object.entries(listaTemas())) console.log(` ${t.padEnd(14)} ${terminos.map((x) => x.replace(/_/g, ' ')).join(', ')}`);
|
|
109
|
+
console.log(`\nEstilos: 2d (variantes: ${Object.keys(VARIANTES_2D).join(', ')}), 3d, realista`);
|
|
110
|
+
console.log('Busca con: npx morfeo recursos buscar "tubería" --3d (o --2d --variante clay, o --realista)');
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
if (sub !== 'buscar') throw new ErrorMorfeo('Uso: npx morfeo recursos temas | npx morfeo recursos buscar "término" [--2d|--3d|--realista] [--variante clay]');
|
|
114
|
+
const termino = resto.join(' ').trim();
|
|
115
|
+
if (!termino) throw new ErrorMorfeo('¿Qué busco? npx morfeo recursos buscar "tubería"');
|
|
116
|
+
const estilo = op['2d'] ? '2d' : op.realista ? 'realista' : '3d';
|
|
117
|
+
const t = traducir(termino);
|
|
118
|
+
console.log(`Buscando "${termino}" (${estilo}${op.variante ? ' ' + op.variante : ''}) → ${t.buscar.join(', ')}${t.tema ? ` · tema ${t.tema}` : ''}\n`);
|
|
119
|
+
const r = await buscarRecursos(termino, {estilo, variante: op.variante, fuente: op.fuente});
|
|
120
|
+
if (!r.length) return console.log('Sin resultados. Prueba otra palabra u otro estilo.');
|
|
121
|
+
r.slice(0, 15).forEach((x, i) => console.log(` ${String(i).padStart(2)} ${x.nombre.padEnd(30).slice(0, 30)} ${x.fuente.padEnd(10)} ${String(x.coleccion).padEnd(26).slice(0, 26)} ${x.licencia}`));
|
|
122
|
+
console.log(`\nÚsalo: {tipo: 'recurso', buscar: '${termino}', estilo: '${estilo}'${op.variante ? `, variante: '${op.variante}'` : ''}} (indice: n para elegir otro)`);
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
100
125
|
case 'estilos': {
|
|
101
126
|
const {ESTILOS} = await import('../lib/config.mjs');
|
|
102
127
|
const desc = {
|
package/index.d.ts
CHANGED
|
@@ -23,6 +23,9 @@ interface EscenaBase {
|
|
|
23
23
|
|
|
24
24
|
export type Estilo = 'datos-curiosos' | 'explicativo' | 'documental' | 'ilustrado' | 'historia' | 'mesa' | 'serie' | 'infografia' | '3d' | 'minimal' | (string & {});
|
|
25
25
|
|
|
26
|
+
/** Objetos 3D incluidos para capas {tipo: 'modelo', forma}: mejor opción que un emoji. */
|
|
27
|
+
export type Objeto3D = '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';
|
|
28
|
+
|
|
26
29
|
/** Momento: segundos desde que empieza la escena, una palabra de `say` ("orcas"), "voz", "fin", o "palabra+0.3". */
|
|
27
30
|
export type Momento = number | string;
|
|
28
31
|
export type Entrada = 'escala' | 'rebote' | 'aparecer' | 'subir' | 'caer' | 'girar' | 'deslizar-izq' | 'deslizar-der' | 'deslizar-arriba' | 'deslizar-abajo' | 'ninguna';
|
|
@@ -74,7 +77,7 @@ export type Capa = CapaBase &
|
|
|
74
77
|
| {tipo: 'marca'; tam?: number}
|
|
75
78
|
| {tipo: 'particulas'; particulas?: 'burbujas' | 'polvo' | 'estrellas' | 'nieve' | 'confeti'; cantidad?: number; color?: string}
|
|
76
79
|
| {tipo: 'grupo'; capas?: Capa[]; capa?: Capa; repetir?: number; disposicion?: 'circulo' | 'fila' | 'columna' | 'cuadricula' | 'pila' | 'aleatorio'; radio?: number; espacio?: number; espacioV?: number; columnas?: number; orientar?: boolean | number; retraso?: number; area?: [number, number]; girarAzar?: number; desorden?: number}
|
|
77
|
-
| {tipo: 'modelo'; src?: string; forma?: 'moneda' | 'billete' | 'cubo' | 'esfera' | 'lingote'; color?: string; metal?: number; rugosidad?: number; ancho?: number; alto?: number; tamano?: number; rotacion?: [number, number, number]; girar3d?: [number, number, number]; cantidad?: number; crecer?: boolean; duracion?: number; distancia?: number; camaraY?: number; luz?: number; reflejos?: number}
|
|
80
|
+
| {tipo: 'modelo'; src?: string; forma?: 'moneda' | 'billete' | 'cubo' | 'esfera' | 'lingote' | Objeto3D; color?: string; metal?: number; rugosidad?: number; ancho?: number; alto?: number; tamano?: number; rotacion?: [number, number, number]; girar3d?: [number, number, number]; cantidad?: number; crecer?: boolean; duracion?: number; distancia?: number; camaraY?: number; luz?: number; reflejos?: number; /** tuberia */ puntos?: [number, number, number][]; radio?: number; material?: 'pvc' | 'cobre' | 'acero' | 'vidrio'; agua?: boolean; velocidad?: number; colorAgua?: string; gotas?: number; /** muro */ columnas?: number; filas?: number; colorLadrillo?: string; colorMezcla?: string; /** viga */ largo?: number; vertical?: boolean; /** escalera */ escalones?: number; /** suelo */ capas?: [string, number][]}
|
|
78
81
|
| {tipo: 'componente'; nombre: string; props?: Record<string, unknown>}
|
|
79
82
|
| {tipo: 'personaje'; nombre: string; pose?: string; poses?: {en: Momento; pose: string}[]; hablar?: boolean; ancho?: number}
|
|
80
83
|
| {tipo: 'escena3d'; src: string; camara?: {en?: Momento; pos: [number, number, number]; mirar?: [number, number, number]; fov?: number}[]; animacion?: boolean | string | string[]; velocidad?: number; mostrar?: {nodo: string; en: Momento}[]; ocultar?: {nodo: string; en: Momento}[]; fondo?: string | null; niebla?: [number, number]; luz?: number; luzAmbiente?: number; sol?: [number, number, number]; reflejos?: number; sombras?: boolean; ancho?: number; alto?: number}
|
|
@@ -87,6 +90,8 @@ export type Capa = CapaBase &
|
|
|
87
90
|
| {tipo: 'onda'; barras?: number; ancho?: number; alto?: number; color?: string; siempre?: boolean}
|
|
88
91
|
| {tipo: 'globo'; texto: string; cola?: 'abajo' | 'arriba' | 'izq' | 'der' | 'ninguna'; fondo?: string; color?: string; borde?: string; tam?: number; maxAncho?: number}
|
|
89
92
|
| {tipo: 'seguir'; usuario?: string; texto?: string; pulsar?: number}
|
|
93
|
+
| {tipo: 'recurso'; buscar: string; estilo?: '2d' | '3d' | 'realista'; variante?: 'color' | 'clay' | 'glass' | 'kawaii' | 'retro' | 'pastel' | 'sticker' | 'skeuo' | 'plush' | 'anime' | 'luxe' | 'chrome' | 'liquid' | 'bauhaus' | 'brutal' | 'gothic' | 'linea'; fuente?: 'kenney' | 'polyhaven' | 'iconify' | 'polypizza'; indice?: number; color?: string; ancho?: number; alto?: number; girar3d?: [number, number, number]; rotacion?: [number, number, number]}
|
|
94
|
+
| {tipo: 'escenario'; nombre: Escenario; parte?: 'fondo' | 'frente'; [opcion: string]: unknown}
|
|
90
95
|
| {tipo: 'ventana'; diametro?: number; src?: string; capas?: Capa[]; borde?: 'metal' | 'ninguno' | (string & {}); grosor?: number; remaches?: number | false; zoom?: number | [number, number]; fondo?: string}
|
|
91
96
|
);
|
|
92
97
|
|
|
@@ -94,7 +99,11 @@ export type FondoEscena =
|
|
|
94
99
|
| {tipo: 'color'; color: string}
|
|
95
100
|
| {tipo: 'degradado'; colores: string[]; angulo?: number}
|
|
96
101
|
| {tipo: 'imagen'; src?: string; generar?: string; oscurecer?: number; desenfoque?: number}
|
|
97
|
-
| {tipo: 'video'; src: string; oscurecer?: number}
|
|
102
|
+
| {tipo: 'video'; src: string; oscurecer?: number}
|
|
103
|
+
| {tipo: 'escenario'; nombre: Escenario; oscurecer?: number; [opcion: string]: unknown};
|
|
104
|
+
|
|
105
|
+
/** Escenarios ilustrados incluidos (animados). bosque: paneo, luna {cara, cantar, bajar, a}; cuarto: dia, parte; tunel: llegar; pasillo: golpe. */
|
|
106
|
+
export type Escenario = 'bosque' | 'cuarto' | 'departamento' | 'tunel' | 'pasillo';
|
|
98
107
|
|
|
99
108
|
interface EscenaMotorBase extends EscenaBase {
|
|
100
109
|
capas?: Capa[];
|
|
@@ -110,11 +119,11 @@ interface EscenaMotorBase extends EscenaBase {
|
|
|
110
119
|
export type EscenaMotor =
|
|
111
120
|
| (EscenaMotorBase & {type?: 'capas'})
|
|
112
121
|
| (EscenaMotorBase & {type: 'titular'; texto: string; etiqueta?: string; numero?: number | string; sub?: string; y?: number; tam?: number})
|
|
113
|
-
| (EscenaMotorBase & {type: 'dato'; valor: number; de?: number; prefijo?: string; sufijo?: string; decimales?: number; curva?: 'suave' | 'lineal' | 'exponencial'; formato?: 'grande' | 'lcd' | 'chip'; texto?: string; imagen?: string | {generar: string}; tam?: number; y?: number; en?: Momento; duracion?: number})
|
|
122
|
+
| (EscenaMotorBase & {type: 'dato'; valor: number; de?: number; prefijo?: string; sufijo?: string; decimales?: number; curva?: 'suave' | 'lineal' | 'exponencial'; formato?: 'grande' | 'lcd' | 'chip'; texto?: string; objeto?: Objeto3D; imagen?: string | {generar: string}; tam?: number; y?: number; en?: Momento; duracion?: number})
|
|
114
123
|
| (EscenaMotorBase & {type: 'lista'; titulo?: string; cabecera?: string; items: (string | {texto: string; check?: Momento | boolean})[]; ancho?: number; y?: number})
|
|
115
124
|
| (EscenaMotorBase & {type: 'telefono'; src?: string; capasPantalla?: Capa[]; texto?: string; ancho?: number; y?: number})
|
|
116
125
|
| (EscenaMotorBase & {type: 'comparar'; a: string | {texto: string; imagen?: string; color?: string}; b: string | {texto: string; imagen?: string; color?: string}; at?: string})
|
|
117
|
-
| (EscenaMotorBase & {type: 'eleccion'; pregunta?: string; opciones: (string | {texto: string; sub?: string; emoji?: string; icono?: Icono; imagen?: string; color?: string; fondo?: string; en?: Momento})[]; formato?: 'cartas' | 'botones'; comenta?: string | false; enComenta?: Momento; y?: number})
|
|
126
|
+
| (EscenaMotorBase & {type: 'eleccion'; pregunta?: string; opciones: (string | {texto: string; sub?: string; objeto?: Objeto3D; emoji?: string; icono?: Icono; imagen?: string; color?: string; fondo?: string; en?: Momento})[]; formato?: 'cartas' | 'botones'; comenta?: string | false; enComenta?: Momento; y?: number})
|
|
118
127
|
| (Partial<EscenaMotorBase> & {type: 'cta'});
|
|
119
128
|
|
|
120
129
|
export interface Tema {
|
package/lib/config.mjs
CHANGED
|
@@ -23,7 +23,9 @@ export const TIPOS_CLASICOS = ['title', 'year', 'text', 'icon', 'compare', 'card
|
|
|
23
23
|
export const TIPOS_MOTOR = ['capas', 'titular', 'dato', 'lista', 'telefono', 'comparar', 'eleccion', 'cta'];
|
|
24
24
|
export const TIPOS_ESCENA = [...new Set([...TIPOS_CLASICOS, ...TIPOS_MOTOR])];
|
|
25
25
|
export const ESTILOS = ['datos-curiosos', 'explicativo', 'documental', 'ilustrado', 'historia', 'mesa', 'serie', 'infografia', '3d', 'minimal'];
|
|
26
|
-
export const TIPOS_CAPA = ['imagen', 'video', 'texto', 'contador', 'chip', 'etiqueta', 'numero', 'tarjeta', 'telefono', 'forma', 'icono', 'emoji', 'marca', 'particulas', 'grupo', 'modelo', 'componente', 'barra', 'medidor', 'votacion', 'estado', 'noticia', 'chat', 'onda', 'globo', 'seguir', 'personaje', 'escena3d', 'ventana'];
|
|
26
|
+
export const TIPOS_CAPA = ['imagen', 'video', 'texto', 'contador', 'chip', 'etiqueta', 'numero', 'tarjeta', 'telefono', 'forma', 'icono', 'emoji', 'marca', 'particulas', 'grupo', 'modelo', 'componente', 'barra', 'medidor', 'votacion', 'estado', 'noticia', 'chat', 'onda', 'globo', 'seguir', 'personaje', 'escena3d', 'ventana', 'escenario', 'recurso'];
|
|
27
|
+
export const ESCENARIOS = ['bosque', 'cuarto', 'departamento', 'tunel', 'pasillo'];
|
|
28
|
+
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'];
|
|
27
29
|
export const PROVEEDORES_VOZ = ['kokoro', 'elevenlabs', 'openai'];
|
|
28
30
|
export const MUSICAS = ['lofi', 'lofi2', 'chillpop', 'synth', 'house', 'ballad', 'trap'];
|
|
29
31
|
|
|
@@ -106,7 +108,7 @@ export async function cargarProyecto(proyecto = process.cwd()) {
|
|
|
106
108
|
const estilosPropios = cfg.estilos ?? {};
|
|
107
109
|
const estilo = cfg.estilo ?? 'datos-curiosos';
|
|
108
110
|
const assetsDir = path.resolve(proyecto, cfg.assets ?? 'assets');
|
|
109
|
-
const ctx = {proyecto, marca, voces: cfg.voces ?? {}, musicas: cfg.musicas ?? {}, errores, estilo, estilosPropios, assetsDir};
|
|
111
|
+
const ctx = {proyecto, marca, voces: cfg.voces ?? {}, musicas: cfg.musicas ?? {}, errores, estilo, estilosPropios, assetsDir, personajes: cfg.personajes ?? {}};
|
|
110
112
|
if (!ESTILOS.includes(estilo) && !estilosPropios[estilo]) errores.push(`estilo "${estilo}" no existe. Opciones: ${[...ESTILOS, ...Object.keys(estilosPropios)].join(', ')}`);
|
|
111
113
|
const componentes = {};
|
|
112
114
|
for (const [nombre, ruta] of Object.entries(cfg.componentes ?? {})) {
|
|
@@ -155,6 +157,32 @@ export async function cargarProyecto(proyecto = process.cwd()) {
|
|
|
155
157
|
};
|
|
156
158
|
}
|
|
157
159
|
|
|
160
|
+
/** Capas {tipo: 'personaje'} que piden un personaje (o una pose) que no está en el config. */
|
|
161
|
+
function revisarPersonajes(x, personajes, err) {
|
|
162
|
+
if (Array.isArray(x)) return x.forEach((v) => revisarPersonajes(v, personajes, err));
|
|
163
|
+
if (!x || typeof x !== 'object') return;
|
|
164
|
+
if (x.tipo === 'personaje') {
|
|
165
|
+
const def = personajes[x.nombre];
|
|
166
|
+
if (!def) err(`el personaje "${x.nombre}" no existe. Defínelo en morfeo.config.mjs → personajes. Hay: ${Object.keys(personajes).join(', ') || 'ninguno'}`);
|
|
167
|
+
else {
|
|
168
|
+
const poses = Object.keys(def.poses ?? {});
|
|
169
|
+
for (const p of [x.pose, ...(x.poses ?? []).map((k) => k?.pose)].filter(Boolean))
|
|
170
|
+
if (!poses.includes(p)) err(`el personaje "${x.nombre}" no tiene la pose "${p}". Tiene: ${poses.join(', ')}`);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
for (const v of Object.values(x)) if (v && typeof v === 'object') revisarPersonajes(v, personajes, err);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/** Fondos y capas {tipo: 'escenario'} con un nombre que no existe. */
|
|
177
|
+
function revisarEscenarios(x, err) {
|
|
178
|
+
if (Array.isArray(x)) return x.forEach((v) => revisarEscenarios(v, err));
|
|
179
|
+
if (!x || typeof x !== 'object') return;
|
|
180
|
+
if (x.tipo === 'escenario' && !ESCENARIOS.includes(x.nombre)) err(`el escenario "${x.nombre}" no existe. Hay: ${ESCENARIOS.join(', ')}`);
|
|
181
|
+
if (x.tipo === 'modelo' && x.forma && !x.src && !FORMAS_3D.includes(x.forma)) err(`la forma 3D "${x.forma}" no existe. Hay: ${FORMAS_3D.join(', ')}`);
|
|
182
|
+
if (x.objeto && typeof x.objeto === 'string' && !FORMAS_3D.includes(x.objeto)) err(`el objeto 3D "${x.objeto}" no existe. Hay: ${FORMAS_3D.join(', ')}`);
|
|
183
|
+
for (const v of Object.values(x)) if (v && typeof v === 'object') revisarEscenarios(v, err);
|
|
184
|
+
}
|
|
185
|
+
|
|
158
186
|
const CAMPOS_ARCHIVO = new Set(['src', 'imagen']);
|
|
159
187
|
/** Busca `src`/`imagen` que apunten a assets/ y avisa de los que no existen. */
|
|
160
188
|
function revisarArchivos(x, assetsDir, err, ruta = '') {
|
|
@@ -175,6 +203,10 @@ function validarCapas(capas, donde, err) {
|
|
|
175
203
|
if (!c || typeof c !== 'object') return err(`${d} debe ser un objeto`);
|
|
176
204
|
if (!TIPOS_CAPA.includes(c.tipo)) return err(`${d}: tipo "${c.tipo}" no existe. Opciones: ${TIPOS_CAPA.join(', ')}`);
|
|
177
205
|
if ((c.tipo === 'imagen' || c.tipo === 'video') && !c.src && !c.generar) err(`${d}: falta \`src\` (archivo en assets/) o \`generar\` (descripción para la IA)`);
|
|
206
|
+
if (c.tipo === 'recurso') {
|
|
207
|
+
if (!c.buscar) err(`${d}: falta \`buscar\` (qué objeto: 'tubería', 'casa', 'pizza'...)`);
|
|
208
|
+
if (c.estilo && !['2d', '3d', 'realista'].includes(c.estilo)) err(`${d}: estilo "${c.estilo}" no existe. Usa '2d', '3d' o 'realista'`);
|
|
209
|
+
}
|
|
178
210
|
if (c.capas) validarCapas(c.capas, d, err);
|
|
179
211
|
if (c.capa) validarCapas([c.capa], d, err);
|
|
180
212
|
});
|
|
@@ -252,7 +284,8 @@ function validarVideo(id, v, ctx) {
|
|
|
252
284
|
const scenes = v.scenes.map((s0, i) => {
|
|
253
285
|
const n = `escena ${i + 1}`;
|
|
254
286
|
if (!s0 || typeof s0 !== 'object') return err(`${n} debe ser un objeto`), s0;
|
|
255
|
-
|
|
287
|
+
// sin `type`: escena de capas (con o sin capas: el fondo y los subtítulos mientras habla)
|
|
288
|
+
const s = {...s0, type: s0.type ?? 'capas'};
|
|
256
289
|
if (!permitidos.includes(s.type))
|
|
257
290
|
err(
|
|
258
291
|
TIPOS_CLASICOS.includes(s.type)
|
|
@@ -280,5 +313,7 @@ function validarVideo(id, v, ctx) {
|
|
|
280
313
|
if (v.hud != null) validarCapas(v.hud, 'hud', err);
|
|
281
314
|
// archivos de assets/ que piden las escenas y el HUD: mejor avisar ahora que a mitad del render
|
|
282
315
|
revisarArchivos({scenes, hud: v.hud, tema: v.tema}, ctx.assetsDir, err);
|
|
316
|
+
revisarPersonajes({scenes, hud: v.hud}, ctx.personajes, err);
|
|
317
|
+
revisarEscenarios({scenes, hud: v.hud, tema: v.tema}, err);
|
|
283
318
|
return {...v, estilo, voice, music: normalizarMusica(v.music, ctx, err), scenes};
|
|
284
319
|
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
// Diccionario temático: términos en español → búsquedas en inglés para las bibliotecas de recursos.
|
|
2
|
+
// Sirve para {tipo: 'recurso', buscar: 'tubería'} y para que la IA elija ilustraciones según la temática.
|
|
3
|
+
// Cada término puede tener varias búsquedas (de la más específica a la más general).
|
|
4
|
+
// Si un término no está, se busca tal cual (y en inglés si viene en inglés).
|
|
5
|
+
|
|
6
|
+
export const TEMAS = {
|
|
7
|
+
objetos: {
|
|
8
|
+
barril: ['barrel'], caja: ['box', 'crate'], bolsa: ['bag'], maleta: ['suitcase'], paraguas: ['umbrella'], sombrero: ['hat'], gorra: ['cap'],
|
|
9
|
+
lentes: ['glasses'], zapato: ['shoe'], camisa: ['shirt'], reloj_de_pulsera: ['watch'], anillo: ['ring'], bolso: ['handbag', 'bag'], cubeta: ['bucket'],
|
|
10
|
+
escoba: ['broom'], tijeras: ['scissors'], cuerda: ['rope'], cadena: ['chain'], iman: ['magnet'], brujula: ['compass'], mapa: ['map'], sobre: ['envelope'],
|
|
11
|
+
carta_correo: ['letter', 'envelope'], paquete: ['package', 'parcel', 'box'], etiqueta: ['tag', 'label'], campana: ['bell'], trompeta: ['trumpet'],
|
|
12
|
+
guitarra: ['guitar'], piano: ['piano'], tambor: ['drum'], pincel: ['paintbrush', 'brush'], pintura: ['paint'], cuadro: ['painting', 'picture frame'],
|
|
13
|
+
vela_aromatica: ['candle'], jarron: ['vase'], maceta: ['pot', 'plant pot'], tetera: ['teapot'], olla: ['pot', 'cooking pot'], sarten: ['pan', 'frying pan'],
|
|
14
|
+
cuchara: ['spoon'], tenedor: ['fork'], frasco: ['jar'], lata: ['can'], extintor: ['fire extinguisher'], botiquin: ['first aid kit'], escalera_mano: ['ladder'],
|
|
15
|
+
silla_ruedas: ['wheelchair'], muleta: ['crutch'], carrito: ['cart', 'shopping cart'], tienda_campana: ['tent'], fogata: ['campfire'], hacha: ['axe'],
|
|
16
|
+
arco: ['bow'], flecha: ['arrow'], bote_basura: ['trash can', 'bin'], periodico: ['newspaper'], revista: ['magazine'], boleto: ['ticket'], pasaporte: ['passport'],
|
|
17
|
+
},
|
|
18
|
+
hogar: {
|
|
19
|
+
casa: ['house', 'home'], cuarto: ['bedroom', 'room'], cama: ['bed'], sillon: ['sofa', 'couch', 'armchair'], sofa: ['sofa', 'couch'],
|
|
20
|
+
silla: ['chair'], mesa: ['table'], escritorio: ['desk'], lampara: ['lamp'], librero: ['bookcase', 'shelf'], refrigerador: ['fridge', 'refrigerator'],
|
|
21
|
+
estufa: ['stove', 'oven'], horno: ['oven'], lavabo: ['sink', 'bathroom sink'], fregadero: ['kitchen sink', 'sink'], inodoro: ['toilet'], banera: ['bathtub'],
|
|
22
|
+
regadera: ['shower'], espejo: ['mirror'], puerta: ['door'], ventana: ['window'], televisor: ['television', 'tv'], planta: ['plant', 'potted plant'],
|
|
23
|
+
alfombra: ['rug'], ropero: ['wardrobe', 'closet'], buro: ['nightstand'], cajon: ['drawer', 'cabinet'], lavadora: ['washer', 'washing machine'], microondas: ['microwave'], licuadora: ['blender'], ventilador: ['fan'], almohada: ['pillow'], cortina: ['curtain'], reloj: ['clock'], llave: ['key'], candado: ['padlock', 'lock'], cocina: ['kitchen'], bano: ['bathroom'],
|
|
24
|
+
},
|
|
25
|
+
construccion: {
|
|
26
|
+
tuberia: ['pipe', 'pipes'], tubo: ['pipe'], valvula: ['valve'], llave_de_agua: ['faucet', 'tap'], drenaje: ['drain', 'sewer'], manguera: ['hose'],
|
|
27
|
+
ladrillo: ['brick'], muro: ['wall', 'brick wall'], pared: ['wall'], viga: ['beam', 'girder'], columna: ['column', 'pillar'], escalera: ['ladder', 'stairs'],
|
|
28
|
+
andamio: ['scaffold'], cemento: ['cement', 'concrete'], concreto: ['concrete'], grua: ['crane'], excavadora: ['excavator', 'digger'], casco: ['helmet', 'hard hat'],
|
|
29
|
+
martillo: ['hammer'], taladro: ['drill'], llave_inglesa: ['wrench'], desarmador: ['screwdriver'], sierra: ['saw'], pala: ['shovel'], carretilla: ['wheelbarrow'],
|
|
30
|
+
cono: ['traffic cone', 'cone'], barrera: ['barrier'], tunel: ['tunnel'], puente: ['bridge'], tanque: ['tank'], cable: ['cable', 'wire'], tornillo: ['screw', 'bolt'],
|
|
31
|
+
molino: ['windmill', 'building mill', 'mill'], granja: ['farm', 'barn'], granero: ['barn'], pozo: ['well'], muelle: ['dock', 'pier'], mina: ['mine'], castillo_piedra: ['castle'],
|
|
32
|
+
herramienta: ['tool', 'toolbox'], caja_de_herramientas: ['toolbox'], valla: ['fence'], cerca: ['fence'],
|
|
33
|
+
},
|
|
34
|
+
ciudad: {
|
|
35
|
+
edificio: ['building', 'skyscraper'], rascacielos: ['skyscraper'], tienda: ['shop', 'store'], calle: ['road', 'street'], semaforo: ['traffic light'],
|
|
36
|
+
farola: ['street light', 'lamp post'], banca: ['bench'], parada: ['bus stop'], hidrante: ['fire hydrant', 'hydrant'], basura: ['trash can', 'garbage'],
|
|
37
|
+
buzon: ['mailbox'], hospital_edificio: ['hospital'], escuela_edificio: ['school'], iglesia: ['church'], mercado: ['market'], torre: ['tower'], fuente: ['fountain'], letrero: ['sign', 'billboard'], estacion: ['station'], metro: ['subway', 'train'],
|
|
38
|
+
},
|
|
39
|
+
transporte: {
|
|
40
|
+
auto: ['car'], carro: ['car'], coche: ['car'], camion: ['truck'], autobus: ['bus'], ambulancia: ['ambulance'], patrulla: ['police car', 'police'],
|
|
41
|
+
bomberos: ['firetruck', 'fire truck'], taxi: ['taxi'], moto: ['motorcycle', 'motorbike'], bicicleta: ['bicycle', 'bike'], tren: ['train'], avion: ['airplane', 'plane'],
|
|
42
|
+
barco: ['boat', 'ship'], cohete: ['rocket'], helicoptero: ['helicopter'], submarino: ['submarine'],
|
|
43
|
+
},
|
|
44
|
+
naturaleza: {
|
|
45
|
+
arbol: ['tree'], pino: ['pine tree', 'pine'], flor: ['flower'], pasto: ['grass'], roca: ['rock', 'stone'], piedra: ['stone', 'rock'], montana: ['mountain'],
|
|
46
|
+
hongo: ['mushroom'], hoja: ['leaf'], tronco: ['log', 'tree trunk'], arbusto: ['bush', 'shrub'], cactus: ['cactus'], nube: ['cloud'], sol: ['sun'], luna: ['moon'],
|
|
47
|
+
lluvia: ['rain'], rayo: ['lightning', 'thunder'], fuego: ['fire', 'campfire'], agua: ['water'], rio: ['river'], lago: ['lake'], isla: ['island'], volcan: ['volcano'],
|
|
48
|
+
},
|
|
49
|
+
oceano: {
|
|
50
|
+
ballena: ['whale'], tiburon: ['shark'], pez: ['fish'], pulpo: ['octopus'], medusa: ['jellyfish'], delfin: ['dolphin'], tortuga: ['turtle'], cangrejo: ['crab'],
|
|
51
|
+
concha: ['shell', 'seashell'], coral: ['coral'], ancla: ['anchor'], faro: ['lighthouse'], burbuja: ['bubble'],
|
|
52
|
+
},
|
|
53
|
+
animales: {
|
|
54
|
+
perro: ['dog'], gato: ['cat'], oso: ['bear'], leon: ['lion'], tigre: ['tiger'], elefante: ['elephant'], caballo: ['horse'], vaca: ['cow'], cerdo: ['pig'],
|
|
55
|
+
oveja: ['sheep'], gallina: ['chicken'], pato: ['duck'], conejo: ['rabbit', 'bunny'], raton: ['mouse'], mono: ['monkey'], pajaro: ['bird'], buho: ['owl'],
|
|
56
|
+
serpiente: ['snake'], arana: ['spider'], abeja: ['bee'], mariposa: ['butterfly'], dinosaurio: ['dinosaur'], zorro: ['fox'], lobo: ['wolf'], pinguino: ['penguin'],
|
|
57
|
+
},
|
|
58
|
+
comida: {
|
|
59
|
+
manzana: ['apple'], platano: ['banana'], pizza: ['pizza'], hamburguesa: ['burger', 'hamburger'], papas: ['fries', 'french fries'], pan: ['bread'],
|
|
60
|
+
queso: ['cheese'], huevo: ['egg'], pastel: ['cake'], galleta: ['cookie'], helado: ['ice cream'], cafe: ['coffee', 'coffee cup'], taza: ['cup', 'mug'],
|
|
61
|
+
dona: ['donut', 'doughnut'], tacos: ['taco'], sandia: ['watermelon'], uva: ['grapes'], fresa: ['strawberry'], zanahoria: ['carrot'], tomate: ['tomato'],
|
|
62
|
+
carne: ['meat', 'steak'], pollo: ['chicken leg', 'chicken'], pescado: ['fish'], sopa: ['soup', 'bowl'], ramen: ['ramen', 'noodles'], refresco: ['soda', 'can'],
|
|
63
|
+
botella: ['bottle'], vaso: ['glass'], plato: ['plate'], cubiertos: ['cutlery', 'fork'],
|
|
64
|
+
},
|
|
65
|
+
dinero: {
|
|
66
|
+
dinero: ['money', 'cash'], moneda: ['coin'], billete: ['banknote', 'money'], cartera: ['wallet'], banco: ['bank'], tarjeta: ['credit card'], alcancia: ['piggy bank'],
|
|
67
|
+
oro: ['gold', 'gold bar'], diamante: ['diamond', 'gem'], grafica: ['chart', 'graph'], cofre: ['chest', 'treasure chest'], tesoro: ['treasure'], caja_fuerte: ['safe'],
|
|
68
|
+
},
|
|
69
|
+
tecnologia: {
|
|
70
|
+
computadora: ['computer', 'laptop'], laptop: ['laptop'], celular: ['phone', 'smartphone'], telefono: ['phone'], robot: ['robot'], chip: ['chip', 'microchip'],
|
|
71
|
+
bateria: ['battery'], enchufe: ['plug', 'socket'], foco: ['light bulb', 'bulb'], camara: ['camera'], audifonos: ['headphones'], microfono: ['microphone'],
|
|
72
|
+
control: ['gamepad', 'controller'], consola: ['console', 'game console'], antena: ['antenna'], satelite: ['satellite'], dron: ['drone'], servidor: ['server'],
|
|
73
|
+
wifi: ['wifi'], nube_de_datos: ['cloud computing', 'cloud'], codigo: ['code'], teclado: ['keyboard'], raton_pc: ['computer mouse'],
|
|
74
|
+
},
|
|
75
|
+
espacio: {
|
|
76
|
+
planeta: ['planet'], estrella: ['star'], astronauta: ['astronaut'], ovni: ['ufo'], galaxia: ['galaxy'], meteorito: ['meteor', 'asteroid'], telescopio: ['telescope'],
|
|
77
|
+
nave: ['spaceship', 'spacecraft'], tierra: ['earth', 'globe'], marte: ['mars'], cometa: ['comet'],
|
|
78
|
+
},
|
|
79
|
+
cuerpo: {
|
|
80
|
+
corazon: ['heart'], cerebro: ['brain'], pulmon: ['lungs'], hueso: ['bone'], craneo: ['skull'], diente: ['tooth'], ojo: ['eye'], mano: ['hand'], musculo: ['muscle'],
|
|
81
|
+
sangre: ['blood', 'blood drop'], adn: ['dna'], virus: ['virus'], bacteria: ['bacteria'], pastilla: ['pill', 'medicine'], jeringa: ['syringe'], hospital: ['hospital'],
|
|
82
|
+
doctor: ['doctor'], termometro: ['thermometer'], estetoscopio: ['stethoscope'], cama_de_hospital: ['hospital bed'],
|
|
83
|
+
},
|
|
84
|
+
escuela: {
|
|
85
|
+
libro: ['book'], cuaderno: ['notebook'], lapiz: ['pencil'], pluma: ['pen'], mochila: ['backpack'], pizarron: ['blackboard', 'chalkboard'], regla: ['ruler'],
|
|
86
|
+
calculadora: ['calculator'], globo_terraqueo: ['globe'], microscopio: ['microscope'], matraz: ['flask', 'lab flask'], diploma: ['diploma'], birrete: ['graduation cap'],
|
|
87
|
+
},
|
|
88
|
+
deporte: {
|
|
89
|
+
balon: ['ball', 'football'], futbol: ['soccer ball', 'football'], basquetbol: ['basketball'], beisbol: ['baseball'], tenis: ['tennis'], trofeo: ['trophy'],
|
|
90
|
+
medalla: ['medal'], pesa: ['dumbbell'], bicicleta_estatica: ['exercise bike'], caminadora: ['treadmill'], silbato: ['whistle'], cronometro: ['stopwatch'],
|
|
91
|
+
golf: ['golf'], patineta: ['skateboard'], carrera: ['race', 'racing'],
|
|
92
|
+
},
|
|
93
|
+
misterio: {
|
|
94
|
+
fantasma: ['ghost'], calavera: ['skull'], tumba: ['grave', 'tombstone'], ataud: ['coffin'], vela: ['candle'], linterna: ['lantern', 'flashlight'], lupa: ['magnifying glass'],
|
|
95
|
+
huella: ['footprint', 'fingerprint'], cuchillo: ['knife'], arana_red: ['spider web'], murcielago: ['bat'], calabaza: ['pumpkin'], castillo: ['castle'], llave_antigua: ['old key', 'key'],
|
|
96
|
+
},
|
|
97
|
+
juegos: {
|
|
98
|
+
videojuego: ['video game', 'gamepad'], dado: ['dice'], carta: ['playing card', 'card'], ajedrez: ['chess'], moneda_juego: ['coin'], corona: ['crown'], espada: ['sword'],
|
|
99
|
+
escudo: ['shield'], cofre_juego: ['chest'], gema: ['gem', 'crystal'], pocion: ['potion'], bandera: ['flag'], meta: ['finish line', 'flag'],
|
|
100
|
+
},
|
|
101
|
+
fiestas: {
|
|
102
|
+
regalo: ['gift', 'present'], globo: ['balloon'], pastel_cumpleanos: ['birthday cake', 'cake'], arbol_navidad: ['christmas tree'], nieve: ['snow', 'snowman'],
|
|
103
|
+
munneco_nieve: ['snowman'], fuegos_artificiales: ['fireworks'], confeti: ['confetti'], mascara: ['mask'],
|
|
104
|
+
},
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
/** Quita acentos, ñ → n, minúsculas y espacios → _ (las claves del diccionario están así). */
|
|
108
|
+
export const clave = (s) =>
|
|
109
|
+
String(s)
|
|
110
|
+
.normalize('NFD')
|
|
111
|
+
.replace(/[̀-ͯ]/g, '')
|
|
112
|
+
.toLowerCase()
|
|
113
|
+
.trim()
|
|
114
|
+
.replace(/\s+/g, '_');
|
|
115
|
+
|
|
116
|
+
const INDICE = new Map();
|
|
117
|
+
for (const [tema, terminos] of Object.entries(TEMAS)) for (const [t, q] of Object.entries(terminos)) if (!INDICE.has(t)) INDICE.set(t, {tema, termino: t, buscar: q});
|
|
118
|
+
|
|
119
|
+
/** Búsquedas en inglés para un término en español (o el término tal cual si no está). */
|
|
120
|
+
export function traducir(termino) {
|
|
121
|
+
const k = clave(termino);
|
|
122
|
+
if (INDICE.has(k)) return INDICE.get(k);
|
|
123
|
+
// plural simple: "tuberías" → "tuberia", "árboles" → "arbol"
|
|
124
|
+
for (const s of [k.replace(/es$/, ''), k.replace(/s$/, '')]) if (INDICE.has(s)) return INDICE.get(s);
|
|
125
|
+
return {tema: null, termino: k, buscar: [String(termino).trim()]};
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/** Lista de temas con sus términos (para `morfeo recursos temas` y para la IA). */
|
|
129
|
+
export const listaTemas = () => Object.fromEntries(Object.entries(TEMAS).map(([t, v]) => [t, Object.keys(v)]));
|
package/lib/guion.mjs
CHANGED
|
@@ -4,6 +4,7 @@ import fs from 'node:fs';
|
|
|
4
4
|
import os from 'node:os';
|
|
5
5
|
import path from 'node:path';
|
|
6
6
|
import {ErrorMorfeo, MUSICAS} from './config.mjs';
|
|
7
|
+
import {listaTemas} from './diccionario.mjs';
|
|
7
8
|
|
|
8
9
|
export const MODELO_GUION = 'claude-opus-5-5';
|
|
9
10
|
export const TIPOS_IA = ['title', 'year', 'text', 'icon', 'compare', 'card', 'dialog'];
|
|
@@ -87,6 +88,9 @@ export async function esquema() {
|
|
|
87
88
|
});
|
|
88
89
|
}
|
|
89
90
|
|
|
91
|
+
// objetos 3D del catálogo de Morfeo (remotion/motor/objetos3d.tsx)
|
|
92
|
+
const OBJETOS = ['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'];
|
|
93
|
+
|
|
90
94
|
const ESTILOS_MOTOR = ['explicativo', 'documental', 'ilustrado', 'historia', 'mesa', 'minimal'];
|
|
91
95
|
|
|
92
96
|
const INSTRUCCIONES_MOTOR = `Eres guionista y director de arte de videos cortos verticales (YouTube Shorts / TikTok / Reels) de 25 a 60 segundos.
|
|
@@ -99,11 +103,19 @@ Tipos de escena:
|
|
|
99
103
|
- dato: un número que cuenta hasta "valor" (con "prefijo"/"sufijo", p. ej. sufijo " m"), "texto" corto debajo. "curva": "exponencial" si crece muchísimo.
|
|
100
104
|
- lista: tarjeta con "titulo" y 2-4 "items"; cada item se palomea cuando la voz dice su palabra "check".
|
|
101
105
|
- comparar: "a" contra "b" (textos cortos); "at" es la palabra de "say" en la que aparece "b".
|
|
102
|
-
- capas: una ilustración
|
|
106
|
+
- capas: una ilustración, en este orden de preferencia:
|
|
107
|
+
1. "recurso": un objeto concreto de la biblioteca (palabra del diccionario de abajo, según la temática del video), con "recurso_estilo"
|
|
108
|
+
("3d" low-poly, "realista" o "2d"); usa el mismo estilo en todo el video.
|
|
109
|
+
2. "objeto": un objeto 3D simbólico del catálogo (corazón, trofeo, cohete...).
|
|
110
|
+
3. "imagen_ia" si se pide arte (descripción breve de UN objeto o personaje aislado).
|
|
111
|
+
4. "emoji" SOLO si nada de lo anterior encaja (es el último recurso),
|
|
103
112
|
más un "chip" opcional (dato corto como "×5" o "230 dB") que aparece en la palabra "en".
|
|
104
|
-
- eleccion: final abierto para que el público comente: "pregunta" y 2 "opciones" ({texto, sub, emoji
|
|
113
|
+
- eleccion: final abierto para que el público comente: "pregunta" y 2 "opciones" ({texto, sub, objeto}; emoji solo si ningún objeto encaja). Úsala al final en series o historias.
|
|
105
114
|
Varía los tipos. "at", "check" y "en" deben ser palabras que aparezcan tal cual en "say".
|
|
106
|
-
No incluyas la escena final de "sígueme": morfeo la agrega sola
|
|
115
|
+
No incluyas la escena final de "sígueme": morfeo la agrega sola.
|
|
116
|
+
|
|
117
|
+
Diccionario de recursos por tema (usa estas palabras en "recurso"):
|
|
118
|
+
${Object.entries(listaTemas()).map(([t, v]) => `- ${t}: ${v.join(', ')}`).join('\n')}`;
|
|
107
119
|
|
|
108
120
|
export async function esquemaMotor() {
|
|
109
121
|
const {z} = await import('zod');
|
|
@@ -123,12 +135,15 @@ export async function esquemaMotor() {
|
|
|
123
135
|
a: z.string().nullable(),
|
|
124
136
|
b: z.string().nullable(),
|
|
125
137
|
at: z.string().nullable(),
|
|
138
|
+
recurso: z.string().nullable(),
|
|
139
|
+
recurso_estilo: z.enum(['3d', 'realista', '2d']).nullable(),
|
|
140
|
+
objeto: z.enum(OBJETOS).nullable(),
|
|
126
141
|
emoji: z.string().nullable(),
|
|
127
142
|
imagen_ia: z.string().nullable(),
|
|
128
143
|
chip: z.string().nullable(),
|
|
129
144
|
en: z.string().nullable(),
|
|
130
145
|
pregunta: z.string().nullable(),
|
|
131
|
-
opciones: z.array(z.object({texto: z.string(), sub: z.string(), emoji: z.string()})).nullable(),
|
|
146
|
+
opciones: z.array(z.object({texto: z.string(), sub: z.string(), objeto: z.enum(OBJETOS).nullable(), emoji: z.string().nullable()})).nullable(),
|
|
132
147
|
});
|
|
133
148
|
return z.object({id: z.string(), title: z.string(), music: z.enum(MUSICAS), scenes: z.array(escena)});
|
|
134
149
|
}
|
|
@@ -160,12 +175,15 @@ function aEscenaMotor(e, conImagenes) {
|
|
|
160
175
|
break;
|
|
161
176
|
case 'eleccion':
|
|
162
177
|
si('pregunta', e.pregunta);
|
|
163
|
-
out.opciones = (e.opciones ?? []).slice(0, 3);
|
|
178
|
+
out.opciones = (e.opciones ?? []).slice(0, 3).map(({texto, sub, objeto, emoji}) => ({texto, sub, ...(objeto ? {objeto} : emoji ? {emoji} : {})}));
|
|
164
179
|
break;
|
|
165
180
|
case 'capas': {
|
|
166
181
|
const capas = [];
|
|
167
|
-
|
|
168
|
-
|
|
182
|
+
// recurso de la biblioteca > objeto 3D > imagen con IA > emoji (último recurso)
|
|
183
|
+
if (e.recurso) capas.push({tipo: 'recurso', buscar: e.recurso, estilo: e.recurso_estilo ?? '3d', x: 540, y: 1000, entrada: 'rebote', flotar: 10});
|
|
184
|
+
else if (e.objeto) capas.push({tipo: 'modelo', forma: e.objeto, ancho: 680, alto: 680, x: 540, y: 1000, girar3d: [0, 40, 0], entrada: 'rebote', flotar: 12});
|
|
185
|
+
else if (e.imagen_ia && conImagenes) capas.push({tipo: 'imagen', generar: e.imagen_ia, ancho: 640, x: 540, y: 1000, entrada: 'escala', flotar: true});
|
|
186
|
+
else capas.push({tipo: 'modelo', forma: 'estrella', ancho: 600, alto: 600, x: 540, y: 1000, girar3d: [0, 40, 0], entrada: 'rebote'});
|
|
169
187
|
if (e.chip) capas.push({tipo: 'chip', texto: e.chip, x: 760, y: 1280, en: e.en ?? 'fin-0.5', entrada: 'rebote', sonido: 'pop'});
|
|
170
188
|
out.capas = capas;
|
|
171
189
|
break;
|