morfeo 0.2.0 → 0.3.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.
- package/bin/morfeo.mjs +1 -0
- package/index.d.ts +17 -2
- package/lib/config.mjs +2 -2
- package/lib/estructura.mjs +15 -4
- package/lib/instalar.mjs +18 -2
- package/lib/whisper.mjs +18 -2
- package/package.json +1 -1
- package/remotion/kit/diorama.tsx +489 -0
- package/remotion/kit/index.ts +1 -0
- package/remotion/kit/tres.tsx +123 -7
- package/remotion/motor/Video.tsx +13 -7
- package/remotion/motor/capas.tsx +2 -0
- package/remotion/motor/fichas.tsx +255 -0
- package/remotion/motor/modelo3d.tsx +1 -1
- package/remotion/motor/subtitulos.tsx +2 -2
- package/remotion/motor/tema.ts +20 -1
package/bin/morfeo.mjs
CHANGED
|
@@ -168,6 +168,7 @@ async function main() {
|
|
|
168
168
|
serie: 'series con personaje y capítulos: HUD (días, barras), cartas VS, "comenta", subtítulos en bloque',
|
|
169
169
|
infografia: 'infografía plana: medidores, barras, globos, pantalla dividida, rayos X',
|
|
170
170
|
'3d': 'escenas 3D con recorrido de cámara (tu .glb), una palabra grande a la vez',
|
|
171
|
+
maqueta: 'diorama 3D low-poly con corte de tierra: una palabra al centro, etiquetas con flecha, cortes rápidos (kit diorama)',
|
|
171
172
|
minimal: 'oscuro y limpio, para lo que sea',
|
|
172
173
|
};
|
|
173
174
|
for (const e of ESTILOS) console.log(` ${e.padEnd(16)} ${desc[e] ?? ''}`);
|
package/index.d.ts
CHANGED
|
@@ -21,7 +21,7 @@ interface EscenaBase {
|
|
|
21
21
|
audio?: string;
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
export type Estilo = 'datos-curiosos' | 'explicativo' | 'documental' | 'ilustrado' | 'historia' | 'mesa' | 'serie' | 'infografia' | '3d' | 'minimal' | (string & {});
|
|
24
|
+
export type Estilo = 'datos-curiosos' | 'explicativo' | 'documental' | 'ilustrado' | 'historia' | 'mesa' | 'serie' | 'infografia' | '3d' | 'maqueta' | 'minimal' | (string & {});
|
|
25
25
|
|
|
26
26
|
/** Objetos 3D incluidos para capas {tipo: 'modelo', forma}: mejor opción que un emoji. */
|
|
27
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';
|
|
@@ -84,6 +84,14 @@ export type Capa = CapaBase &
|
|
|
84
84
|
| {tipo: 'terminal'; lineas: (string | {texto: string; en?: Momento; salida?: boolean; color?: string})[]; titulo?: string; ancho?: number; tam?: number; fondo?: string; velocidad?: number}
|
|
85
85
|
| {tipo: 'nombre'; texto: string; sub?: string; tam?: number; fondo?: string; colorTexto?: string; borde?: string}
|
|
86
86
|
| {tipo: 'accion'; texto: string; tam?: number; color?: string; fondo?: string; colorTexto?: string}
|
|
87
|
+
/** Tarjetas de datos (estilo maqueta): se llenan o palomean solas desde `en`. */
|
|
88
|
+
| {tipo: 'reloj'; de?: number; a?: number; max?: number; segmentos?: [number, number, string][]; etiqueta?: string | false; tam?: number; color?: string; duracion?: number}
|
|
89
|
+
| {tipo: 'cronometro'; de?: number; a?: number; max?: number; unidad?: 'seg' | 'min'; tam?: number; color?: string; duracion?: number}
|
|
90
|
+
| {tipo: 'termometro'; valor: number; de?: number; min?: number; max?: number; unidad?: string; alto?: number; color?: string; decimales?: number; mostrar?: boolean}
|
|
91
|
+
| {tipo: 'recipientes'; n?: number; valor?: number; forma?: 'garrafon' | 'vaso'; total?: string; color?: string; tamPieza?: number; duracion?: number}
|
|
92
|
+
| {tipo: 'ficha'; titulo?: string; foto?: string; campos: [string, string][]; ancho?: number; color?: string; fondoFoto?: string}
|
|
93
|
+
| {tipo: 'checklist'; items: (string | {texto: string; en?: Momento; pendiente?: boolean})[]; titulo?: string; tam?: number; color?: string; cada?: number}
|
|
94
|
+
| {tipo: 'mapa'; ruta?: [number, number][]; distancia?: string; ancho?: number; color?: string; fondo?: string; inclinar?: number; duracion?: number}
|
|
87
95
|
| {tipo: 'cursor'; a: [number, number]; clic?: Momento; duracion?: number; tam?: number; color?: string}
|
|
88
96
|
| {tipo: 'texto'; texto: string; tam?: number; color?: string; acento?: string; fuente?: 'titulo' | 'texto' | 'mono' | (string & {}); peso?: number; mayusculas?: boolean; contorno?: number; colorContorno?: string; fondo?: string; relleno?: string; radio?: number; maxAncho?: number | 'auto'; alinear?: 'center' | 'left' | 'right'; interlineado?: number; escribir?: boolean | number; subrayado?: boolean}
|
|
89
97
|
| {tipo: 'contador'; de?: number; a: number; prefijo?: string; sufijo?: string; decimales?: number; miles?: string; decimal?: string; duracion?: number; curva?: 'suave' | 'lineal' | 'exponencial'; formato?: 'grande' | 'lcd' | 'chip'; tam?: number; color?: string; fondo?: string}
|
|
@@ -144,6 +152,9 @@ interface EscenaMotorBase extends EscenaBase {
|
|
|
144
152
|
rotulo?: string;
|
|
145
153
|
rotuloEn?: Momento;
|
|
146
154
|
rotuloTam?: number;
|
|
155
|
+
/** 'normal' | 'contorno' (blanco con contorno, clave en acento) | 'pastilla' (caja oscura inclinada: ¡CUIDADO!). Por defecto, el del estilo. */
|
|
156
|
+
rotuloEstilo?: 'normal' | 'contorno' | 'pastilla';
|
|
157
|
+
rotuloInclinar?: number;
|
|
147
158
|
/** Palabras clave de la escena para los subtítulos (la que lleva caja o color de acento en cada frase). */
|
|
148
159
|
claves?: string[];
|
|
149
160
|
/** Ajustes del tema solo para esta escena. */
|
|
@@ -171,7 +182,7 @@ export interface Tema {
|
|
|
171
182
|
colores: {texto: string; acento: string; acento2: string; suave: string; tarjeta: string; tarjetaTexto: string; borde: string};
|
|
172
183
|
fuentes: {titulo: string; texto: string; mono: string};
|
|
173
184
|
tituloMayusculas: boolean;
|
|
174
|
-
subtitulos: {tipo: 'karaoke' | 'seleccion' | 'pop' | 'bloque' | 'palabra' | 'caja' | 'ninguno'; y: number; tam: number; color: string; pendiente: string; resalte: string; maxPalabras: number; mayusculas?: boolean; contorno?: number; fondo?: string};
|
|
185
|
+
subtitulos: {tipo: 'karaoke' | 'seleccion' | 'pop' | 'bloque' | 'palabra' | 'caja' | 'ninguno'; y: number; tam: number; color: string; pendiente: string; resalte: string; maxPalabras: number; mayusculas?: boolean | 'inicial'; contorno?: number; fondo?: string};
|
|
175
186
|
transicion: Transicion;
|
|
176
187
|
/** Ritmo de las animaciones: duraciones (s) y curva de las entradas. */
|
|
177
188
|
movimiento?: {entrada?: number; salida?: number; transicion?: number; curva?: 'suave' | 'resorte' | 'seca'};
|
|
@@ -182,6 +193,8 @@ export interface Tema {
|
|
|
182
193
|
desenfoqueBordes?: number;
|
|
183
194
|
vinetaColor?: string;
|
|
184
195
|
};
|
|
196
|
+
/** Rótulo de cada escena: estilo 'normal' | 'contorno' | 'pastilla', inclinación, altura (px) y tamaño. */
|
|
197
|
+
rotulo?: {estilo?: 'normal' | 'contorno' | 'pastilla'; inclinar?: number; y?: number; tam?: number};
|
|
185
198
|
marca: 'esquina' | 'arriba' | 'ninguna';
|
|
186
199
|
zoomLento: number;
|
|
187
200
|
}
|
|
@@ -233,6 +246,8 @@ export interface Video {
|
|
|
233
246
|
mundo?: Capa[];
|
|
234
247
|
/** Pastilla fija arriba para series: 'GYM CON CIENCIA · PARTE 5'. */
|
|
235
248
|
serie?: string;
|
|
249
|
+
/** 'largo' = explicador de 90–130 s (lo asume el estilo maqueta): cambia las reglas de `morfeo revisar`. */
|
|
250
|
+
formato?: 'corto' | 'largo';
|
|
236
251
|
/** Ajustes del estilo para este video (colores, fondo, subtítulos...). */
|
|
237
252
|
tema?: Partial<Tema> & Record<string, unknown>;
|
|
238
253
|
/** Título para publicar. */
|
package/lib/config.mjs
CHANGED
|
@@ -22,8 +22,8 @@ const parecidas = (nombre, lista, n = 3) => {
|
|
|
22
22
|
export const TIPOS_CLASICOS = ['title', 'year', 'text', 'icon', 'compare', 'card', 'dialog', 'aliens', 'memory', 'morph', 'cta'];
|
|
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
|
-
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', 'escenario', 'recurso', 'dato', 'medida', 'trazo', 'contorno', 'fuente', 'seccion', 'insignia', 'sello', 'cursor', 'terminal', 'nombre', 'accion'];
|
|
25
|
+
export const ESTILOS = ['datos-curiosos', 'explicativo', 'documental', 'ilustrado', 'historia', 'mesa', 'serie', 'infografia', '3d', 'maqueta', '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', 'escenario', 'recurso', 'dato', 'medida', 'trazo', 'contorno', 'fuente', 'seccion', 'insignia', 'sello', 'cursor', 'terminal', 'nombre', 'accion', 'reloj', 'cronometro', 'termometro', 'recipientes', 'ficha', 'checklist', 'mapa'];
|
|
27
27
|
export const ESCENARIOS = ['bosque', 'cuarto', 'departamento', 'tunel', 'pasillo'];
|
|
28
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'];
|
|
29
29
|
export const PROVEEDORES_VOZ = ['kokoro', 'elevenlabs', 'openai'];
|
package/lib/estructura.mjs
CHANGED
|
@@ -18,6 +18,12 @@
|
|
|
18
18
|
// - Series: una pastilla fija arriba ("TITO DESDE CERO · EP. 1", "GYM CON CIENCIA · PARTE 5") y contadores persistentes.
|
|
19
19
|
// - Cierre con una acción concreta, no solo "sígueme": elegir A o B en comentarios, "escribe PASO si entendiste",
|
|
20
20
|
// "mándaselo al que dice que...", cuenta regresiva para la próxima parte.
|
|
21
|
+
//
|
|
22
|
+
// Formato largo "explicador de maqueta" (2026-11, 4 videos de 102–132 s: castor, tinaco, buey, caseta de cobro):
|
|
23
|
+
// - Un solo diorama 3D low-poly con corte de tierra; 24–25 cortes por minuto (uno cada ~2,4 s) entre ángulos del
|
|
24
|
+
// mismo mundo. Una palabra a la vez al centro, rótulos con contorno, etiquetas con flecha pegadas a los objetos.
|
|
25
|
+
// - Gancho "Imagina que eres un castor…" o "¿Cómo se hace una caseta de cobro?"; cierre "¡Felicidades, eres…!" o un
|
|
26
|
+
// VS de "ya lo sabía / no lo sabía". Aquí 90–130 s está bien: el mundo y los cortes sostienen la atención.
|
|
21
27
|
|
|
22
28
|
/** Las reglas, en el orden en que se le explican a la IA y a quien escribe un guion. */
|
|
23
29
|
export const REGLAS = [
|
|
@@ -30,7 +36,7 @@ export const REGLAS = [
|
|
|
30
36
|
{id: 'escalada', regla: 'Que suba la apuesta', como: 'Cada escena más grave, más grande o más rara que la anterior; lo más fuerte cerca del final, no al principio.'},
|
|
31
37
|
{id: 'cierre', regla: 'Cierre con una acción concreta', como: 'Pide algo fácil y específico: elegir A o B en comentarios, escribir una palabra ("escribe PASO"), mandárselo a alguien ("mándaselo al que dice que…") o esperar la parte 2. "Sígueme" solo no basta.'},
|
|
32
38
|
{id: 'bucle', regla: 'Final que conecta con el inicio', como: 'La última frase puede regresar a la primera imagen o dejar una pregunta abierta, para que se vuelva a ver.'},
|
|
33
|
-
{id: 'duracion', regla: 'Entre 30 y 65 segundos', como: 'La mayoría de los de referencia dura ~1 minuto. Más de 75 s solo si es una serie
|
|
39
|
+
{id: 'duracion', regla: 'Entre 30 y 65 segundos (o 90–130 en maqueta)', como: 'La mayoría de los de referencia dura ~1 minuto. Más de 75 s solo si es una serie, una historia o un explicador de maqueta (estilo maqueta: 90–130 s con un corte de cámara cada 2–3 s dentro del mismo mundo).'},
|
|
34
40
|
];
|
|
35
41
|
|
|
36
42
|
/** Las reglas como texto para el prompt de la IA. */
|
|
@@ -38,7 +44,7 @@ export const REGLAS_TEXTO = `Estructura (medida en videos virales; síguela):
|
|
|
38
44
|
${REGLAS.map((r, i) => `${i + 1}. ${r.regla}: ${r.como}`).join('\n')}`;
|
|
39
45
|
|
|
40
46
|
const palabrasDe = (s) => String(s ?? '').trim().split(/\s+/).filter(Boolean);
|
|
41
|
-
const GANCHOS = /^(
|
|
47
|
+
const GANCHOS = /^(¿|¡|imagina|y si|cómo|como se|si tu|si te|si un|qué pasaría|que pasaria|este es|esta es|esto es|nadie|nunca|todos|a \S+ |hay |tu |tú |te |tienes|cuando)/i;
|
|
42
48
|
|
|
43
49
|
/**
|
|
44
50
|
* Revisa la estructura de un video y devuelve consejos (no bloquea el render).
|
|
@@ -54,6 +60,8 @@ export function revisarEstructura(v, d) {
|
|
|
54
60
|
if (!cuerpo.length) return out;
|
|
55
61
|
const primera = cuerpo[0];
|
|
56
62
|
const clasico = (v.estilo ?? 'datos-curiosos') === 'datos-curiosos';
|
|
63
|
+
// formato largo: explicador de maqueta (o `formato: 'largo'` en el video)
|
|
64
|
+
const largo = v.estilo === 'maqueta' || v.formato === 'largo';
|
|
57
65
|
|
|
58
66
|
// 1) gancho
|
|
59
67
|
const dicho = String(primera.say ?? '').trim();
|
|
@@ -67,7 +75,9 @@ export function revisarEstructura(v, d) {
|
|
|
67
75
|
const es = d?.escenas?.filter((e) => e.tipo !== 'cta') ?? [];
|
|
68
76
|
if (es.length) {
|
|
69
77
|
const dur = d.duracion;
|
|
70
|
-
if (dur >
|
|
78
|
+
if (largo && dur > 140) add('duracion', 'idea', `Dura ${Math.round(dur)} s: los explicadores de maqueta de referencia van de 100 a 130 s.`);
|
|
79
|
+
else if (largo && dur < 60) add('duracion', 'idea', `Dura ${Math.round(dur)} s: en maqueta los de referencia duran 100–130 s; hay espacio para otra pregunta y su respuesta.`);
|
|
80
|
+
else if (!largo && dur > 75) add('duracion', 'idea', `Dura ${Math.round(dur)} s: los de referencia rondan 60 s. Si no es una serie o una historia, recorta (o usa estilo maqueta para un explicador largo).`);
|
|
71
81
|
if (dur < 20) add('duracion', 'idea', `Dura ${Math.round(dur)} s: un poco más de desarrollo (una pregunta y su respuesta) suele retener mejor.`);
|
|
72
82
|
const e0 = es[0];
|
|
73
83
|
if (e0 && e0.habla?.[0] > 0.5) add('gancho', 'aviso', `La voz tarda ${e0.habla[0].toFixed(1)} s en empezar: debe arrancar de inmediato.`);
|
|
@@ -77,7 +87,8 @@ export function revisarEstructura(v, d) {
|
|
|
77
87
|
// algo que cambie a media escena: capas con `en`, cámara con rango, toma con movimiento, o un mundo vivo
|
|
78
88
|
const capas = s.capas ?? [];
|
|
79
89
|
const cambia = capas.some((c) => c.en != null && c.en !== 0 && c.en !== 'inicio') || Array.isArray(s.camara?.zoom) || s.toma?.a || capas.some((c) => c.mover || c.cambios);
|
|
80
|
-
if (
|
|
90
|
+
if (largo && voz > 4.5) add('ritmo', 'idea', `Escena ${i + 1}: ${voz.toFixed(1)} s en una sola toma; en maqueta la cámara corta cada 2–3 s. Pártela en dos tomas.`);
|
|
91
|
+
else if (voz > 6 && !cambia && !v.mundo?.length) add('ritmo', 'aviso', `Escena ${i + 1}: ${voz.toFixed(1)} s de voz sin que nada cambie. Pártela en dos o haz que algo entre a media frase.`);
|
|
81
92
|
else if (voz > 9) add('ritmo', 'idea', `Escena ${i + 1}: ${voz.toFixed(1)} s de voz en una sola escena; en los de referencia ninguna idea pasa de ~5 s.`);
|
|
82
93
|
});
|
|
83
94
|
}
|
package/lib/instalar.mjs
CHANGED
|
@@ -22,7 +22,7 @@ import {
|
|
|
22
22
|
venvListo,
|
|
23
23
|
versionPython,
|
|
24
24
|
} from './entorno.mjs';
|
|
25
|
-
import {whisperListo, instalarWhisper} from './whisper.mjs';
|
|
25
|
+
import {faltaParaWhisper, whisperListo, instalarWhisper} from './whisper.mjs';
|
|
26
26
|
|
|
27
27
|
const ok = (m) => console.log(` ✔ ${m}`);
|
|
28
28
|
const no = (m) => console.log(` ✘ ${m}`);
|
|
@@ -154,7 +154,7 @@ export async function instalar(op = {}) {
|
|
|
154
154
|
if (process.platform === 'darwin' && spawnSync('xcode-select', ['-p'], {stdio: 'ignore'}).status !== 0) {
|
|
155
155
|
no('Whisper se compila y necesita las herramientas de Apple.');
|
|
156
156
|
info('Corre: xcode-select --install y cuando termine: npx morfeo instalar --whisper');
|
|
157
|
-
} else {
|
|
157
|
+
} else if (await herramientasWhisper(op)) {
|
|
158
158
|
await instalarWhisper();
|
|
159
159
|
ok('Whisper listo');
|
|
160
160
|
}
|
|
@@ -164,6 +164,22 @@ export async function instalar(op = {}) {
|
|
|
164
164
|
console.log('\nTodo listo. Prueba: npx morfeo nuevo mi-canal && cd mi-canal && npx morfeo render');
|
|
165
165
|
}
|
|
166
166
|
|
|
167
|
+
/** Revisa git, make y cmake antes de compilar whisper.cpp; en Mac con Homebrew ofrece instalar cmake. */
|
|
168
|
+
async function herramientasWhisper(op) {
|
|
169
|
+
let falta = faltaParaWhisper();
|
|
170
|
+
if (falta.length === 1 && falta[0] === 'cmake' && process.platform === 'darwin' && tiene('brew')) {
|
|
171
|
+
if (await preguntar('Whisper necesita cmake para compilarse. ¿Lo instalo con Homebrew?', {...op, porDefecto: true})) {
|
|
172
|
+
correr('brew', ['install', 'cmake']);
|
|
173
|
+
falta = faltaParaWhisper();
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
if (!falta.length) return true;
|
|
177
|
+
no(`Whisper se compila y falta: ${falta.join(', ')}.`);
|
|
178
|
+
if (process.platform === 'darwin') info(`Corre: brew install ${falta.join(' ')} y cuando termine: npx morfeo instalar --whisper`);
|
|
179
|
+
else info(`Instálalo con tu gestor de paquetes (por ejemplo: sudo apt install ${falta.join(' ')} ) y luego: npx morfeo instalar --whisper`);
|
|
180
|
+
return false;
|
|
181
|
+
}
|
|
182
|
+
|
|
167
183
|
/** Devuelve el estado de cada pieza; con imprimir=true lo muestra. */
|
|
168
184
|
export async function doctor({imprimir = true} = {}) {
|
|
169
185
|
const estado = [];
|
package/lib/whisper.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Whisper (opcional): tiempos reales de cada palabra para los subtítulos.
|
|
2
2
|
import {downloadWhisperModel, installWhisperCpp, toCaptions, transcribe} from '@remotion/install-whisper-cpp';
|
|
3
|
-
import {execFileSync} from 'node:child_process';
|
|
3
|
+
import {execFileSync, spawnSync} from 'node:child_process';
|
|
4
4
|
import fs from 'node:fs';
|
|
5
5
|
import path from 'node:path';
|
|
6
6
|
import {WHISPER, pythonVenv} from './entorno.mjs';
|
|
@@ -10,8 +10,24 @@ export const MODELO_WHISPER = 'small';
|
|
|
10
10
|
|
|
11
11
|
export const whisperListo = () => fs.existsSync(path.join(WHISPER, `ggml-${MODELO_WHISPER}.bin`)) && fs.existsSync(path.join(WHISPER, '.listo'));
|
|
12
12
|
|
|
13
|
+
/** Herramientas que whisper.cpp necesita para compilarse (en Windows se descarga ya compilado). */
|
|
14
|
+
export function faltaParaWhisper() {
|
|
15
|
+
if (process.platform === 'win32') return [];
|
|
16
|
+
return ['git', 'make', 'cmake'].filter((cmd) => spawnSync('which', [cmd], {stdio: 'ignore'}).status !== 0);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// Mismo lugar donde Remotion busca el ejecutable desde whisper.cpp 1.7.4.
|
|
20
|
+
const compilado = () => fs.existsSync(path.join(WHISPER, 'build', 'bin', process.platform === 'win32' ? 'whisper-cli.exe' : 'whisper-cli'));
|
|
21
|
+
|
|
13
22
|
export async function instalarWhisper() {
|
|
14
|
-
|
|
23
|
+
// Un intento anterior que falló a medias deja la carpeta sin el ejecutable, y Remotion se niega a seguir: se limpia.
|
|
24
|
+
if (fs.existsSync(WHISPER) && !compilado()) fs.rmSync(WHISPER, {recursive: true, force: true});
|
|
25
|
+
try {
|
|
26
|
+
await installWhisperCpp({to: WHISPER, version: VERSION_CPP, printOutput: true});
|
|
27
|
+
} catch (e) {
|
|
28
|
+
if (!compilado()) fs.rmSync(WHISPER, {recursive: true, force: true});
|
|
29
|
+
throw e;
|
|
30
|
+
}
|
|
15
31
|
await downloadWhisperModel({model: MODELO_WHISPER, folder: WHISPER, printOutput: true});
|
|
16
32
|
fs.writeFileSync(path.join(WHISPER, '.listo'), VERSION_CPP);
|
|
17
33
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "morfeo",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
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",
|