morfeo 0.0.0-stage → 0.1.1
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/LICENSE +22 -0
- package/README.md +210 -2
- package/assets/fuentes/Anton.ttf +0 -0
- package/assets/fuentes/JetBrainsMono.ttf +0 -0
- package/assets/fuentes/Montserrat.ttf +0 -0
- package/bin/morfeo.mjs +184 -0
- package/bin/postinstall.mjs +10 -0
- package/index.d.ts +250 -0
- package/lib/config.mjs +284 -0
- package/lib/entorno.mjs +93 -0
- package/lib/guion.mjs +313 -0
- package/lib/imagenes.mjs +99 -0
- package/lib/index.mjs +15 -0
- package/lib/instalar.mjs +193 -0
- package/lib/navegador.mjs +64 -0
- package/lib/personajes.mjs +94 -0
- package/lib/render.mjs +312 -0
- package/lib/voces.mjs +122 -0
- package/lib/whisper.mjs +45 -0
- package/package.json +84 -5
- package/plantilla/assets/.gitkeep +0 -0
- package/plantilla/env.example +7 -0
- package/plantilla/escenas/Barras.tsx +15 -0
- package/plantilla/gitignore +4 -0
- package/plantilla/guiones/cachalote.mjs +24 -0
- package/plantilla/guiones/tutorial.mjs +16 -0
- package/plantilla/morfeo.config.mjs +43 -0
- package/plantilla/package.json +13 -0
- package/plantilla/videos.mjs +25 -0
- package/python/musica.py +221 -0
- package/python/preparar.py +268 -0
- package/python/requirements.txt +5 -0
- package/remotion/Root.tsx +35 -0
- package/remotion/Short.tsx +278 -0
- package/remotion/escenas.tsx +430 -0
- package/remotion/estilo.ts +73 -0
- package/remotion/iconos.ts +326 -0
- package/remotion/index.ts +4 -0
- package/remotion/motor/Video.tsx +121 -0
- package/remotion/motor/capas.tsx +622 -0
- package/remotion/motor/fondo.tsx +148 -0
- package/remotion/motor/modelo3d.tsx +252 -0
- package/remotion/motor/plantillas.tsx +125 -0
- package/remotion/motor/registro.ts +11 -0
- package/remotion/motor/subtitulos.tsx +137 -0
- package/remotion/motor/tema.ts +207 -0
- package/remotion/motor/util.ts +66 -0
- package/remotion/motor/widgets.tsx +248 -0
- package/remotion/piezas.tsx +181 -0
- package/remotion/tipos.ts +43 -0
- 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
|
-
#
|
|
1
|
+
# Morfeo
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
> **Documentación: [ureckchan.github.io/morfeo](https://ureckchan.github.io/morfeo/)** · 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
|
+
}
|