morfeo 0.0.0-stage → 0.1.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.
Files changed (51) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +210 -2
  3. package/assets/fuentes/Anton.ttf +0 -0
  4. package/assets/fuentes/JetBrainsMono.ttf +0 -0
  5. package/assets/fuentes/Montserrat.ttf +0 -0
  6. package/bin/morfeo.mjs +184 -0
  7. package/bin/postinstall.mjs +10 -0
  8. package/index.d.ts +246 -0
  9. package/lib/config.mjs +241 -0
  10. package/lib/entorno.mjs +93 -0
  11. package/lib/guion.mjs +309 -0
  12. package/lib/imagenes.mjs +99 -0
  13. package/lib/index.mjs +15 -0
  14. package/lib/instalar.mjs +193 -0
  15. package/lib/navegador.mjs +64 -0
  16. package/lib/personajes.mjs +94 -0
  17. package/lib/render.mjs +278 -0
  18. package/lib/voces.mjs +122 -0
  19. package/lib/whisper.mjs +45 -0
  20. package/package.json +84 -5
  21. package/plantilla/assets/.gitkeep +0 -0
  22. package/plantilla/env.example +7 -0
  23. package/plantilla/escenas/Barras.tsx +15 -0
  24. package/plantilla/gitignore +4 -0
  25. package/plantilla/guiones/cachalote.mjs +24 -0
  26. package/plantilla/guiones/tutorial.mjs +16 -0
  27. package/plantilla/morfeo.config.mjs +43 -0
  28. package/plantilla/package.json +13 -0
  29. package/plantilla/videos.mjs +25 -0
  30. package/python/musica.py +221 -0
  31. package/python/preparar.py +268 -0
  32. package/python/requirements.txt +5 -0
  33. package/remotion/Root.tsx +35 -0
  34. package/remotion/Short.tsx +278 -0
  35. package/remotion/escenas.tsx +430 -0
  36. package/remotion/estilo.ts +73 -0
  37. package/remotion/iconos.ts +326 -0
  38. package/remotion/index.ts +4 -0
  39. package/remotion/motor/Video.tsx +119 -0
  40. package/remotion/motor/capas.tsx +547 -0
  41. package/remotion/motor/fondo.tsx +149 -0
  42. package/remotion/motor/modelo3d.tsx +252 -0
  43. package/remotion/motor/plantillas.tsx +123 -0
  44. package/remotion/motor/registro.ts +11 -0
  45. package/remotion/motor/subtitulos.tsx +137 -0
  46. package/remotion/motor/tema.ts +202 -0
  47. package/remotion/motor/util.ts +66 -0
  48. package/remotion/motor/widgets.tsx +248 -0
  49. package/remotion/piezas.tsx +181 -0
  50. package/remotion/tipos.ts +43 -0
  51. package/remotion/tres/Capa3D.tsx +153 -0
package/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Licencia de uso de Morfeo
2
+
3
+ Copyright (c) 2026 Uriel Gómez. Todos los derechos reservados.
4
+
5
+ 1. Uso permitido. Puedes instalar y usar Morfeo sin costo para crear videos,
6
+ incluidos videos con fines comerciales. Los videos que generes son tuyos.
7
+
8
+ 2. Lo que no está permitido sin permiso escrito del autor: copiar, modificar,
9
+ redistribuir, sublicenciar o vender el código fuente de Morfeo, ni publicar
10
+ versiones derivadas o paquetes que lo incluyan.
11
+
12
+ 3. Terceros. Morfeo usa software y recursos con sus propias licencias, que
13
+ también debes cumplir:
14
+ - Remotion (https://remotion.dev/license): gratis para personas y empresas
15
+ de hasta 3 personas; empresas más grandes necesitan licencia de Remotion.
16
+ - Voces Kokoro (Apache 2.0), whisper.cpp (MIT), Three.js (MIT), React (MIT).
17
+ - Fuentes Anton, Montserrat y JetBrains Mono (SIL Open Font License 1.1).
18
+ - Si usas servicios externos (ElevenLabs, OpenAI, Anthropic), aplican sus
19
+ términos y lo que cobren.
20
+
21
+ 4. Sin garantía. Morfeo se entrega "tal cual", sin garantía de ningún tipo. El
22
+ autor no es responsable de daños derivados de su uso.
package/README.md CHANGED
@@ -1,3 +1,211 @@
1
- # Temporary Holding Version
1
+ # Morfeo
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ > Galería de estilos y ejemplos: [github.com/UreckChan/morfeo](https://github.com/UreckChan/morfeo).
4
+
5
+ Genera videos verticales animados (Shorts, TikTok, Reels) desde código: escribes las escenas y Morfeo agrega
6
+ la voz, la música, los efectos de sonido, los subtítulos, las animaciones y la miniatura.
7
+
8
+ - **Estilos:** 10 incluidos, y puedes crear los tuyos:
9
+ - `datos-curiosos`: texto grande con contorno e íconos 3D;
10
+ - `explicativo`: papel claro, titulares y tarjetas;
11
+ - `documental`: océano, rayos de luz y datos;
12
+ - `ilustrado`: escenarios ilustrados y personajes;
13
+ - `historia`: siluetas, foco de luz y misterio;
14
+ - `mesa`: objetos 3D y contadores;
15
+ - `serie`: series con personaje, HUD (días, barras), cartas VS y "comenta";
16
+ - `infografia`: medidores, barras, globos y rayos X;
17
+ - `3d`: tus escenas `.glb` con recorrido de cámara;
18
+ - `minimal`: oscuro y limpio.
19
+ - **Motor de capas:** cada escena se arma con imágenes, texto, contadores, chips, tarjetas, teléfonos, formas,
20
+ grupos (formaciones, pilas), modelos 3D o tus propios componentes de React. Todo se anima justo en la palabra que dice la voz.
21
+
22
+ - **Voces:** Kokoro (gratis, en tu computadora, más de 50 voces y mezclas), ElevenLabs, OpenAI, tu propia
23
+ grabación por escena o cualquier servicio que conectes con una función.
24
+ - **Música:** 7 estilos generados sin derechos de autor, o tu propia pista (mp3, wav, flac, ogg). Baja sola cuando habla la voz.
25
+ - **Guiones:** los escribes tú, o Claude escribe el video completo a partir de un tema o de tu texto.
26
+ - **Subtítulos:** palabra por palabra; con Whisper quedan sincronizados con la voz real.
27
+
28
+ ## Instalar
29
+
30
+ Necesitas Node.js 18+. Python 3.10–3.13 también, pero el instalador te ayuda a conseguirlo.
31
+
32
+ ```bash
33
+ npx morfeo nuevo mi-canal
34
+ cd mi-canal
35
+ npm install
36
+ npx morfeo instalar
37
+ npx morfeo render
38
+ ```
39
+
40
+ `morfeo instalar` se corre una sola vez por computadora y deja todo en `~/.morfeo`:
41
+
42
+ - busca Python y, si falta, ofrece instalarlo (Homebrew en Mac, winget en Windows);
43
+ - crea un entorno aislado con las librerías de voz;
44
+ - descarga las voces (~340 MB) y el navegador de render;
45
+ - pregunta si quieres Whisper (~470 MB). En Mac, Whisper necesita `xcode-select --install`.
46
+
47
+ `npx morfeo doctor` dice qué falta.
48
+
49
+ ## Comandos
50
+
51
+ | Comando | Qué hace |
52
+ |---|---|
53
+ | `morfeo nuevo <carpeta>` | Crea un proyecto con un video de ejemplo |
54
+ | `morfeo lista` | Videos del proyecto y cuáles ya están hechos |
55
+ | `morfeo render [id...]` | Genera los que faltan (`--todos` para todos, `--gpu` para usar la tarjeta de video) |
56
+ | `morfeo studio` | Abre el editor en el navegador para ver y ajustar en vivo |
57
+ | `morfeo guion "tema"` | Claude escribe un video completo en `guiones/<id>.json` (`--estilo documental`, `--imagenes`) |
58
+ | `morfeo estilos` | Lista los estilos visuales |
59
+ | `morfeo guion --desde guion.txt` | Convierte tu texto en escenas (con IA, o `--sin-ia`: una escena por frase) |
60
+ | `morfeo instalar` / `doctor` | Prepara o revisa la computadora |
61
+
62
+ ## Un video
63
+
64
+ ```js
65
+ // videos.mjs
66
+ import {defineVideos} from 'morfeo';
67
+
68
+ export default defineVideos({
69
+ cielo: {
70
+ title: '¿Por qué el cielo es azul? 🌤️',
71
+ voice: [['em_alex', 0.6], ['am_michael', 0.4]],
72
+ music: 'lofi',
73
+ scenes: [
74
+ {type: 'title', say: '¿Sabías por qué el cielo es azul?', rows: [['¿POR QUÉ', '#FFF', 150], ['ES AZUL?', '#50DCFF', 230]], icon: 'question'},
75
+ {type: 'year', say: 'Lord Rayleigh lo explicó en mil ochocientos setenta y uno.', show: 'Lo explicó en 1871.', pre: 'RAYLEIGH', year: 1871, sub: 'DISPERSIÓN'},
76
+ {type: 'cta'},
77
+ ],
78
+ },
79
+ });
80
+ ```
81
+
82
+ **Escenas:**
83
+ - `title`: el gancho.
84
+ - `text`: filas grandes de texto.
85
+ - `year`: una fecha.
86
+ - `icon`: ícono grande con etiqueta.
87
+ - `compare`: A vs B, el cambio ocurre en la palabra `at`.
88
+ - `card`: tarjeta tipo código cuyo valor cambia.
89
+ - `dialog`: ventana con un botón que se pulsa en la palabra `click`.
90
+ - `cta`: cierre con tu marca.
91
+
92
+ `say` es lo que dice la voz. `show` es el subtítulo cuando debe ser distinto, por ejemplo con cifras. Las escenas
93
+ `aliens`, `memory` y `morph` son especiales de los videos de ejemplo.
94
+
95
+ ## Estilos y capas
96
+
97
+ Cada video elige un `estilo` (o usa el de `morfeo.config.mjs`). `npx morfeo estilos` los lista.
98
+
99
+ ```js
100
+ // guiones/cachalote.mjs
101
+ export default {
102
+ estilo: 'documental',
103
+ voice: 'narrador',
104
+ music: 'ballad',
105
+ scenes: [
106
+ {say: '¿Sabías que las orcas le tienen miedo a un solo animal?', capas: [
107
+ {tipo: 'imagen', src: 'cachalote.png', ancho: 700, entrada: 'deslizar-der', mover: {a: [480, 980]}, flotar: true},
108
+ {tipo: 'chip', texto: '×5', x: 760, y: 1260, en: 'animal', sonido: 'pop'},
109
+ ]},
110
+ {type: 'dato', say: 'Baja a más de dos mil metros.', show: 'Baja a más de 2.000 metros.', valor: 2000, sufijo: ' m', camara: {zoom: [1.15, 1]}},
111
+ {say: 'Y forman un círculo.', capas: [
112
+ {tipo: 'grupo', repetir: 8, disposicion: 'circulo', radio: 300, orientar: 90, capa: {tipo: 'imagen', src: 'cachalote.png', ancho: 220}},
113
+ ]},
114
+ {type: 'cta'},
115
+ ],
116
+ };
117
+ ```
118
+
119
+ **Tipos de escena:**
120
+ - `capas`: solo tus capas.
121
+ - `titular`: etiqueta "// paso 1", titular con `*palabra*` resaltada y número.
122
+ - `dato`: número que cuenta.
123
+ - `lista`: tarjeta que se palomea.
124
+ - `telefono`
125
+ - `comparar`
126
+ - `cta`
127
+
128
+ Todas aceptan `capas`, `fondo`, `camara` (zoom, paneo, temblor), `transicion` (corte, fundido, deslizar, zoom) y `tema`.
129
+
130
+ **Capas:**
131
+ - `imagen`, `video`, `texto` (con `*resaltado*` y efecto de escribir), `contador` (grande, LCD o chip; lineal o exponencial);
132
+ - `chip`, `etiqueta`, `numero`, `tarjeta`, `telefono`;
133
+ - `forma` (rect, círculo, línea, flecha), `icono`, `emoji`, `marca`, `particulas`;
134
+ - `grupo`: varias capas juntas, o una repetida en círculo, fila, cuadrícula, pila o al azar;
135
+ - `modelo`: tu `.glb`, o formas 3D incluidas como moneda, billete, lingote, cubo y esfera; con `cantidad` y `crecer` forma una pila que crece;
136
+ - `componente`: tu propio React.
137
+
138
+ **Animación de cada capa:**
139
+ - `en` y `hasta` aceptan segundos o una palabra de la voz;
140
+ - `entrada`: escala, rebote, deslizar-izq, caer, girar…;
141
+ - `mover`, `escalar`, `girar`, `flotar` y `latir`;
142
+ - `sonido`: pop, whoosh, tick, glitch o ding.
143
+
144
+ Tus imágenes, videos y modelos van en `assets/`. Para que la IA dibuje, usa `{tipo: 'imagen', generar: 'un oso pardo de frente'}`
145
+ con `imagenes: {proveedor: 'openai', estilo: 'ilustración plana, colores cálidos'}` en el config. El `estilo` mantiene la misma
146
+ línea gráfica en todas las imágenes. Cada imagen se genera una sola vez (caché).
147
+
148
+ **Series y personajes:**
149
+ - `personajes: {neto: {poses: {normal: './neto.png', feliz: './neto-feliz.png'}}}` en el config. Con `referencia: './neto.png'`, una pose escrita como texto (`triste: 'llorando'`) la genera la IA a partir de esa imagen, así sigue siendo el mismo personaje.
150
+ - En la escena: `{tipo: 'personaje', nombre: 'neto', pose: 'normal', poses: [{en: 'enamorado', pose: 'feliz'}], hablar: true}`.
151
+ - `hud: [...]` en el video: capas fijas en todos los cuadros (día, barra "probabilidad 0%", hora). `cambios: [{en: {escena: 3}, valor: 15}]` las actualiza con animación.
152
+ - Escena `eleccion`: cartas VS con "¡COMENTA!", o `formato: 'botones'` (ABRIR / NO ABRIR).
153
+ - Capas de interfaz: `barra`, `medidor`, `votacion`, `estado` (hora, batería, día), `noticia` (cintillo), `chat`, `onda`, `globo` y `seguir`.
154
+ - `imagen` con `cambiarA: {src, en, efecto: 'circulo'}` revela otra imagen, por ejemplo en modo rayos X.
155
+
156
+ **Escenas 3D:**
157
+ - `{tipo: 'escena3d', src: 'casa.glb', camara: [{en: 0, pos: [12, 7, 16], mirar: [0, 0, 0]}, {en: 'drenaje', pos: [5, 1, 9]}], animacion: true, ocultar: [{nodo: 'Tierra', en: 'tierra'}]}`.
158
+ - La cámara recorre la escena en las palabras que indiques.
159
+ - Las animaciones del archivo van sincronizadas con el video.
160
+ - Las piezas se muestran u ocultan por su nombre de Blender.
161
+
162
+ **Estilo propio:** `estilos: {'mi-marca': {base: 'minimal', colores: {acento: '#00E0A0'}, fondo: {tipo: 'imagen', src: 'fondo.png'}}}`.
163
+
164
+ **Componentes propios:** `componentes: {Mapa: './escenas/Mapa.tsx'}`. Reciben `t`, `dur`, `palabras`, `tema` y `cuando('palabra')`.
165
+
166
+ ## Voces
167
+
168
+ ```js
169
+ voice: 'em_alex' // una voz Kokoro
170
+ voice: [['ef_dora', 0.6], ['af_heart', 0.4]] // mezcla
171
+ voice: './voces/mi-voz.npy' // estilo de voz Kokoro propio
172
+ voice: {proveedor: 'elevenlabs', voz: 'VOICE_ID'} // ELEVENLABS_API_KEY en .env
173
+ voice: {proveedor: 'openai', voz: 'nova'} // OPENAI_API_KEY en .env
174
+ voice: {proveedor: async (texto) => './audio.wav'} // tu propio servicio
175
+ ```
176
+
177
+ O graba tú: `{type: 'text', say: 'Hola', audio: './grabaciones/hola.wav', rows: [...]}`.
178
+
179
+ Lo que se pide a servicios de pago se guarda en caché (`.morfeo/cache`). La misma frase no se vuelve a pedir ni a cobrar.
180
+
181
+ ## Música
182
+
183
+ `music: 'lofi'` (también `lofi2`, `chillpop`, `synth`, `house`, `ballad`, `trap`), `music: './musica/cancion.mp3'`,
184
+ `music: {archivo: './musica/cancion.mp3', volumen: 0.7, inicio: 12}` o `music: false`.
185
+
186
+ Para no repetir, define alias en `morfeo.config.mjs` con `voces: {...}` y `musicas: {...}`.
187
+
188
+ ## Claves (.env)
189
+
190
+ Copia `.env.example` como `.env`. Solo llena lo que uses:
191
+
192
+ - `ANTHROPIC_API_KEY`: guiones con IA.
193
+ - `ELEVENLABS_API_KEY` u `OPENAI_API_KEY`: voces por servicio.
194
+
195
+ Nunca subas `.env` a git.
196
+
197
+ ## Desde código
198
+
199
+ ```js
200
+ import {render, guionConIA, guardarVideo} from 'morfeo';
201
+
202
+ const {id, video} = await guionConIA({tema: 'el origen del café'});
203
+ guardarVideo('./guiones', id, video);
204
+ await render({ids: [id]});
205
+ ```
206
+
207
+ ## Licencia
208
+
209
+ Uso gratuito para crear videos, incluidos los comerciales. El código es de Uriel Gómez; no se permite
210
+ redistribuirlo ni modificarlo. Remotion tiene su propia licencia: gratis para personas y equipos de hasta 3 personas.
211
+ Detalles en [LICENSE](LICENSE).
Binary file
Binary file
Binary file
package/bin/morfeo.mjs ADDED
@@ -0,0 +1,184 @@
1
+ #!/usr/bin/env node
2
+ // CLI de Morfeo. `npx morfeo ayuda` para ver los comandos.
3
+ import fs from 'node:fs';
4
+ import path from 'node:path';
5
+ import {ErrorMorfeo} from '../lib/config.mjs';
6
+ import {PAQUETE, VERSION} from '../lib/entorno.mjs';
7
+
8
+ const AYUDA = `Morfeo ${VERSION} · videos verticales animados con voz, música y subtítulos
9
+
10
+ Uso: npx morfeo <comando> [opciones]
11
+
12
+ Primeros pasos
13
+ instalar Prepara tu computadora: Python, voces, navegador y (opcional) Whisper
14
+ --si responde "sí" a todo --whisper / --sin-whisper
15
+ nuevo <carpeta> Crea un proyecto de ejemplo listo para renderizar
16
+ doctor Revisa qué está instalado y qué falta
17
+
18
+ Videos
19
+ lista Muestra los videos del proyecto y cuáles ya están hechos
20
+ render [id...] Genera los videos que faltan (o los que indiques)
21
+ --todos regenera todos
22
+ --gpu usa la tarjeta de video (más rápido; si falla, quítalo)
23
+ studio Abre el editor en el navegador para ver y ajustar en vivo
24
+ estilos Lista los estilos visuales disponibles
25
+
26
+ Guiones
27
+ guion "tema" La IA (Claude) escribe un video completo sobre el tema
28
+ guion --desde f.txt Convierte tu guion en escenas (con IA; --sin-ia: una escena por frase)
29
+ --estilo documental estilo visual (por defecto, el del proyecto)
30
+ --imagenes pide ilustraciones con IA (necesita OPENAI_API_KEY)
31
+ --id nombre --voz em_alex --duracion 40 --idioma "inglés" --tono "humor"
32
+
33
+ Opciones generales
34
+ --proyecto <carpeta> Proyecto a usar (por defecto, la carpeta actual)
35
+ -v, --version Versión
36
+ `;
37
+
38
+ function opciones(argv) {
39
+ const pos = [];
40
+ const op = {};
41
+ for (let i = 0; i < argv.length; i++) {
42
+ const a = argv[i];
43
+ if (a === '-v') op.version = true;
44
+ else if (a === '-h') op.ayuda = true;
45
+ else if (a.startsWith('--')) {
46
+ const [k, v] = a.slice(2).split('=');
47
+ const clave = k.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
48
+ const sig = argv[i + 1];
49
+ const conValor = ['proyecto', 'desde', 'id', 'voz', 'duracion', 'idioma', 'estilo', 'tono', 'musica', 'titulo', 'modelo', 'puerto'];
50
+ if (v != null) op[clave] = v;
51
+ else if (conValor.includes(clave) && sig != null && !sig.startsWith('--')) op[clave] = argv[++i];
52
+ else op[clave] = true;
53
+ } else pos.push(a);
54
+ }
55
+ return {pos, op};
56
+ }
57
+
58
+ async function main() {
59
+ const [cmd = 'ayuda', ...resto] = process.argv.slice(2);
60
+ const {pos, op} = opciones(resto);
61
+ const proyecto = path.resolve(op.proyecto ?? '.');
62
+ if (cmd === '-v' || cmd === '--version' || op.version) return console.log(VERSION);
63
+
64
+ switch (cmd) {
65
+ case 'instalar':
66
+ case 'install': {
67
+ const {instalar} = await import('../lib/instalar.mjs');
68
+ return instalar({si: !!op.si, whisper: op.whisper ? true : op.sinWhisper ? false : null});
69
+ }
70
+ case 'doctor': {
71
+ const {doctor} = await import('../lib/instalar.mjs');
72
+ const {cargarEnv} = await import('../lib/render.mjs');
73
+ cargarEnv(proyecto);
74
+ const e = await doctor();
75
+ if (e.some((x) => !x.bien && !x.nombre.includes('opcional'))) process.exitCode = 1;
76
+ return;
77
+ }
78
+ case 'nuevo':
79
+ case 'new':
80
+ return nuevo(pos[0], op);
81
+ case 'lista':
82
+ case 'ls': {
83
+ const {lista} = await import('../lib/render.mjs');
84
+ const l = await lista({proyecto});
85
+ if (!l.length) return console.log('Este proyecto no tiene videos todavía. Crea uno con: npx morfeo guion "tu tema"');
86
+ for (const v of l) console.log(`[${v.hecho ? '✔' : ' '}] ${v.id.padEnd(16)} ${v.titulo}`);
87
+ console.log(`\n${l.filter((v) => v.hecho).length} de ${l.length} hechos`);
88
+ return;
89
+ }
90
+ case 'render': {
91
+ if (op.gpu) process.env.MORFEO_GL = 'angle';
92
+ const {render} = await import('../lib/render.mjs');
93
+ await render({proyecto, ids: pos, todos: !!op.todos || pos[0] === 'todos'});
94
+ return;
95
+ }
96
+ case 'studio': {
97
+ const {studio} = await import('../lib/render.mjs');
98
+ return studio({proyecto, puerto: op.puerto});
99
+ }
100
+ case 'estilos': {
101
+ const {ESTILOS} = await import('../lib/config.mjs');
102
+ const desc = {
103
+ 'datos-curiosos': 'clásico: morado, texto grande con contorno, íconos pixel 3D (escenas title, year, text...)',
104
+ explicativo: 'papel claro, titulares negro/rojo, etiquetas "// paso", tarjetas y teléfonos (tutoriales)',
105
+ documental: 'azul profundo, rayos de luz, burbujas, chips con datos (naturaleza, ciencia)',
106
+ ilustrado: 'cielo de atardecer o tu escenario ilustrado, personajes y cámara (cuentos, animales)',
107
+ historia: 'interiores oscuros, foco de luz, siluetas y letreros (misterio, crimen)',
108
+ mesa: 'mesa de madera con foco, objetos 3D y contadores LCD (dinero, crecimiento)',
109
+ serie: 'series con personaje y capítulos: HUD (días, barras), cartas VS, "comenta", subtítulos en bloque',
110
+ infografia: 'infografía plana: medidores, barras, globos, pantalla dividida, rayos X',
111
+ '3d': 'escenas 3D con recorrido de cámara (tu .glb), una palabra grande a la vez',
112
+ minimal: 'oscuro y limpio, para lo que sea',
113
+ };
114
+ for (const e of ESTILOS) console.log(` ${e.padEnd(16)} ${desc[e] ?? ''}`);
115
+ console.log('\nÚsalo con estilo: "documental" en el video o en morfeo.config.mjs. Crea los tuyos con estilos: {...} en el config.');
116
+ return;
117
+ }
118
+ case 'guion':
119
+ case 'script':
120
+ return guion(pos, op, proyecto);
121
+ case 'ayuda':
122
+ case 'help':
123
+ case '--help':
124
+ return console.log(AYUDA);
125
+ default:
126
+ console.error(`Comando desconocido: ${cmd}\n`);
127
+ console.log(AYUDA);
128
+ process.exitCode = 1;
129
+ }
130
+ }
131
+
132
+ async function guion(pos, op, proyecto) {
133
+ const {cargarEnv} = await import('../lib/render.mjs');
134
+ const {guionConIA, guionSinIA, guardarVideo} = await import('../lib/guion.mjs');
135
+ const {buscarConfig} = await import('../lib/config.mjs');
136
+ cargarEnv(proyecto);
137
+ if (!buscarConfig(proyecto)) throw new ErrorMorfeo('Corre esto dentro de un proyecto de Morfeo (npx morfeo nuevo mi-canal).');
138
+ const texto = op.desde ? fs.readFileSync(path.resolve(op.desde), 'utf8') : null;
139
+ const tema = pos.join(' ').trim();
140
+ if (!texto && !tema) throw new ErrorMorfeo('Dime el tema: npx morfeo guion "por qué el cielo es azul" o npx morfeo guion --desde mi-guion.txt');
141
+ let r;
142
+ // estilo: el de la opción, o el del proyecto
143
+ let estilo = op.estilo;
144
+ if (!estilo) {
145
+ try {
146
+ estilo = (await (await import('../lib/config.mjs')).cargarProyecto(proyecto)).estilo;
147
+ } catch {}
148
+ }
149
+ if (texto && op.sinIa) r = guionSinIA(texto, {id: op.id, voz: op.voz, musica: op.musica, titulo: op.titulo, estilo});
150
+ else {
151
+ console.log(texto ? 'Claude está convirtiendo tu guion en escenas...' : `Claude está escribiendo un video sobre: ${tema}...`);
152
+ r = await guionConIA({tema, texto, idioma: op.idioma, duracion: op.duracion ? Number(op.duracion) : undefined, tono: op.tono, estilo, imagenes: !!op.imagenes, voz: op.voz, modelo: op.modelo});
153
+ }
154
+ if (op.id) r.id = op.id;
155
+ const {cargarProyecto} = await import('../lib/config.mjs');
156
+ let carpeta = path.join(proyecto, 'guiones');
157
+ try {
158
+ carpeta = (await cargarProyecto(proyecto)).guiones;
159
+ } catch {}
160
+ const {id, archivo} = guardarVideo(carpeta, r.id, r.video);
161
+ console.log(`\nListo: ${path.relative(proyecto, archivo)} (${r.video.scenes.length} escenas)`);
162
+ console.log(`Revísalo y edítalo si quieres; luego: npx morfeo render ${id}`);
163
+ }
164
+
165
+ function nuevo(carpeta, op) {
166
+ if (!carpeta) throw new ErrorMorfeo('Dime el nombre de la carpeta: npx morfeo nuevo mi-canal');
167
+ const destino = path.resolve(carpeta);
168
+ if (fs.existsSync(destino) && fs.readdirSync(destino).length) throw new ErrorMorfeo(`La carpeta ${carpeta} ya existe y no está vacía.`);
169
+ const plantilla = path.join(PAQUETE, 'plantilla');
170
+ fs.cpSync(plantilla, destino, {recursive: true});
171
+ // npm no publica archivos llamados .gitignore: en la plantilla van como "gitignore"
172
+ for (const [de, a] of [['gitignore', '.gitignore'], ['env.example', '.env.example']])
173
+ if (fs.existsSync(path.join(destino, de))) fs.renameSync(path.join(destino, de), path.join(destino, a));
174
+ const nombre = path.basename(destino).toLowerCase().replace(/[^a-z0-9-_]/g, '-');
175
+ const pkg = path.join(destino, 'package.json');
176
+ fs.writeFileSync(pkg, fs.readFileSync(pkg, 'utf8').replace('__NOMBRE__', nombre).replace('__VERSION__', `^${VERSION}`));
177
+ console.log(`Proyecto creado en ${carpeta}/\n\nSiguientes pasos:\n cd ${carpeta}\n npm install\n npx morfeo instalar (solo la primera vez en esta computadora)\n npx morfeo render`);
178
+ }
179
+
180
+ main().catch((e) => {
181
+ if (e instanceof ErrorMorfeo) console.error(`\n✘ ${e.message}`);
182
+ else console.error(e);
183
+ process.exitCode = 1;
184
+ });
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env node
2
+ // Aviso corto después de `npm install morfeo`. No descarga nada: lo pesado lo hace `npx morfeo instalar`.
3
+ try {
4
+ if (process.env.CI || process.env.MORFEO_SILENCIO) process.exit(0);
5
+ const {venvListo, modelosListos} = await import('../lib/entorno.mjs');
6
+ if (venvListo() && modelosListos()) process.exit(0);
7
+ console.log('\n Morfeo instalado. Falta preparar voces y Python (una sola vez, ~10 min):\n\n npx morfeo instalar\n');
8
+ } catch {
9
+ // un aviso nunca debe romper la instalación
10
+ }