--- name: video-engine description: Motor local de video por código del plugin. Una pieza es una carpeta con index.html (HTML/CSS + GSAP + Canvas) donde cada fotograma es función pura del tiempo; el motor la renderiza a MP4 con Chrome headless + ffmpeg, sintetiza voz (edge-tts o Piper), compone música y efectos procedurales, mezcla a -14 LUFS y la revisa con QA automático y hojas de contacto. Úsala como referencia técnica cuando escribas o depures una pieza, cuando un render falle o salga distinto a la vista previa, o cuando necesites un comando del motor (setup, new, voice, check, stills, mix, render, build, preview). Para el flujo completo de pedido → video usa `video-oneshot`. --- # Motor de video Sin claves de API, sin GPU, sin After Effects. Requisitos: Node 18+, Chrome/Edge (o el Chromium de Playwright) y Python 3 solo si hay voz. ffmpeg viene incluido (`ffmpeg-static`). ## Comandos Todos viven en `engine/` (relativo a esta skill) y reciben la carpeta de la pieza. | Comando | Qué hace | |---|---| | `node engine/setup.mjs` | Instala dependencias una vez en `~/.generative-creative-engine` (o `CREATIVE_ENGINE_HOME`) y edge-tts. Idempotente | | `node engine/new.mjs --template ` | Crea la pieza desde `templates/` + `brief.md` | | `node engine/setup-voice.mjs [--dir D:/ai-models]` | Instala la voz expresiva local (Chatterbox + faster-whisper), una vez | | `node engine/voices.mjs list\|add\|kit\|config` | Biblioteca de voces con acento (casting) y llaves | | `node engine/voice.mjs [--takes 3]` | `script.json` dirigido → varias tomas por línea, verificación con Whisper, la mejor → `audio/voice.wav` + `voice.json` + `review/voice.html` (audición). Cachea por línea | | `node engine/preview.mjs ` | Vista previa en vivo en el navegador (sin audio) | | `node engine/check.mjs ` | QA automático → `review/auto.md` + un recorte por hallazgo en `review/findings/`. Sale con 1 si hay FAIL | | `node engine/stills.mjs [--count 12] [--times 1,2.5]` | Fotogramas PNG + `contact-sheet.png` sin renderizar el video (incluye 0.3 s y el último cuadro) | | `node engine/stills.mjs --review` | Las tres hojas del critic en `review/stills/`: `review-beats.png` (cada línea de voz en inicio+0.3 s y a su mitad, más gancho y póster), `review-seam-NN.png` (5 cuadros de −0.2 a +0.2 s en cada corte = inicio de línea), `review-phone-NN.png` (los beats a 360 px de ancho, de a 8) y `review.json` | | `node engine/mix.mjs ` | Música + voz + efectos → `audio/mix.wav` normalizado. `--list-sfx` lista efectos y estilos | | `node engine/render.mjs [--draft] [--from s --to s]` | MP4 (`render/final.mp4`; `--draft` = mitad de resolución → `render/draft.mp4`) | | `node engine/site-footage.mjs /assets/x.mp4 [--from --to --speed --scale 2 --hide header]` | Graba un sitio (propio o con permiso) haciendo scroll, fotograma a fotograma con el reloj de la página controlado: animaciones 3D/ScrollTrigger fluidas. Escribe `x.json` con la posición de scroll de cada fotograma | | `node engine/build.mjs ` | check → mix → render → verificación → hoja de contacto final | Velocidad de referencia: 15 s a 1080×1920 se renderizan en ~15 s con 4 workers (DOM). Canvas pesado o filtros `blur` lo vuelven más lento: mira la línea de progreso. ## Contrato de la pieza ```html
…
``` **Reglas duras** (el QA las vigila; romperlas da renders distintos a la vista previa): 1. Todo movimiento vive en `stage.tl` (GSAP) o en `stage.onFrame((t, frame) => …)`. Nada de `requestAnimationFrame`, `setTimeout`, `setInterval`, transiciones o `@keyframes` CSS sueltos, ni tweens fuera del timeline (`gsap.to` directo). 2. Aleatoriedad solo con `stage.rand()` (`rand.range`, `rand.int`, `rand.pick`, `rand.fork('id')`) y `Stage.noise(seed, x)`. Nunca `Math.random()` ni `Date`. 3. Cada fotograma es **función pura de t**: nada de `x += v` entre fotogramas; calcula la posición desde t. Una simulación se precalcula entera al cargar y se indexa por fotograma. 4. Usa `fromTo` (no `from`) para que el estado inicial sea explícito al buscar hacia atrás. **Ojo: `fromTo` pinta su `from` desde t=0** (`immediateRender`), no desde su inicio: un elemento que "aparece a los 3 s" ya está en su pose inicial en el fotograma 0. Si debe estar ausente antes de su cue, ocúltalo en CSS y usa `tl.set` en la frontera, o pasa `immediateRender: false`. El `to` de un reveal declara `opacity: 1` explícito. 5. Para mostrar u ocultar una escena: `tl.set(sel, {opacity:1}, a).set(sel, {opacity:0}, b)` con la escena en `opacity:0` en CSS. 6. Assets locales en la carpeta de la pieza (`assets/…`). Google Fonts está permitido; cualquier otra URL remota se descarga primero. Una fuente usada solo en `` se pide con `stage.font('800 120px "Archivo"')`. 7. Imágenes: `stage.loadImage('assets/x.png').then((img) => { … })`; el motor espera a que carguen antes del fotograma 0. 8. Nada de `gsap.utils.random`, valores `"random(…)"`, `stagger: {from: 'random'}` ni `repeat: -1` (GSAP sortea con `Math.random` al crear el tween; `-1` vuelve infinito el timeline): `check.mjs` los marca FAIL. Tampoco `.play()` ni `.currentTime =` en la pieza: el motor sincroniza los medios. 9. `