Communitygithub.com

HusseinRagab/motion-reels

Crea Reels verticales (9:16, 1080x1920) de gráficos en movimiento dibujados con código para páginas de diseño de interiores: titulares, planos que se dibujan, barras de antes/después, acercamientos de fotos, muestras de materiales; renderizados fotograma a fotograma con Playwright y ffmpeg.

¿Qué es motion-reels?

Este skill permite a un agente de IA (como Claude Code) generar Reels verticales (9:16) de gráficos en movimiento completamente a partir de código JavaScript, sin necesidad de software de edición de video tradicional. Está diseñado específicamente para páginas de diseño de interiores, pero es adaptable a otros temas visuales. El skill se basa en un sistema de bloques: cada bloque es un pequeño archivo JavaScript que define los elementos visuales (títulos, fotos, dibujos) y su sincronización con una línea de tiempo textual. El motor renderiza cada fotograma en una página de navegador independiente usando Playwright, verifica la maquetación mediante un sistema de 'check' obligatorio, y ensambla el resultado final con voz y/o música usando ffmpeg. El flujo de trabajo está estructurado en 'gates' (pasos secuenciales) que incluyen la aprobación del dueño del proyecto en puntos clave, garantizando control de calidad y evitando errores como textos fuera de zona segura o solapamientos. El skill también soporta Reels en árabe (escritura de derecha a izquierda) y ofrece un look por defecto 'minimalista cálido' que se puede personalizar editando hojas de estilo CSS.

Compatible con✓Claude Code~Codex CLI~Cursor
npx skills add https://github.com/HusseinRagab/reel-motion-kit/tree/HEAD/motion-reels

Preguntar en tu IA favorita

Abre un nuevo chat con esta habilidad de agente ya precargada.

Documentación

¿Qué hace motion-reels?

Gráficos en movimiento dibujados con código para Reels verticales. Cada reel consta de uno o más bloques. Un bloque es un pequeño archivo JavaScript que coloca titulares, dibujos, fotos y muestras en un mundo y dice cuándo aparece cada uno. Cada instante proviene de una línea de tiempo de palabras: ya sea una hoja de ritmos (sin voz) o las sincronizaciones de palabras de una toma de voz. El motor renderiza cada fotograma en su propia página de navegador nueva, comprueba el diseño y ensambla el resultado con voz y/o música.

La persona dueña de la página decide el aspecto, las palabras y qué se publica. Claude nunca publica nada.

Configuración (una vez)

  • Python 3.10+ y ffmpeg/ffprobe en PATH. Luego el setup.ps1 (Windows) o setup.sh (macOS/Linux) del repositorio, o pip install -r requirements.txt + python -m playwright install chromium (o la variable de entorno MG_CHROME = un Chrome existente).
  • Solo voz: pip install -r requirements-voice.txt (faster-whisper; la primera ejecución descarga el modelo de voz).
  • Prueba: python engine/selftest.py debe imprimir SELFTEST OK (las comprobaciones pasan en los ejemplos, las pruebas negativas fallan como deben, los subtítulos en árabe se dividen correctamente, y un render corto + escaneo de fallos + ensamblaje alcanza -14 LUFS). --quick omite el render.

Archivos del motor (todos en engine/): render.py (comprobar / storyboard / fotogramas fijos / video), beats.py, words.py, frames.py, faultscan.py, assemble.py, storyboard_sheet.py, make_grain.py, templates/ (shell, reel.js, reel.css, fuentes).

Una carpeta por reel

<nombre del reel>/
  brief.md          para qué es el reel, la idea principal, el llamado a la acción, fotos disponibles
  beats.txt         las palabras en pantalla, un ritmo por línea (o script.txt + take.wav cuando hay voz)
  words.json        línea de tiempo (de beats.py o words.py)
  photos/           las fotos del dueño (solo aquellas de las que tiene los derechos)
  A.js, B.js ...    bloques
  build/            json de comprobación, fotogramas de storyboard, videos de bloques, archivos render.json, hojas de contacto
  <Reel>_REV01_<fecha>.mp4   el archivo entregable; una corrección posterior se entrega como _REV02, nunca se sobrescribe

Puertas (nunca saltar ni reordenar; detenerse en cada ⛔ para la aprobación del dueño)

  1. Brief. Una idea por reel (una revelación de habitación, una historia de materiales, un cambio de plano, un consejo). Pide las fotos y el identificador. Enumera lo que muestra cada foto. Engancha en los primeros 2 segundos.
  2. Hoja de ritmos (beats.txt): las palabras exactas en pantalla, en orden, un ritmo por línea. Mantén un ritmo corto (2–7 palabras). Pon cada elemento de una lista en su propia línea, para que cada uno tenga tiempo de asentarse. ⛔ El dueño firma las palabras. Después de la aprobación, un cambio de palabras implica re-sincronizar el bloque (y regrabar la voz, si la hay): dilo antes de cambiar algo.
  3. Línea de tiempo.
    • Sin voz: python engine/beats.py beats.txt words.json (ritmo predeterminado 2.6 palabras/s + 0.7 s después de cada ritmo; [+1.5] al final de una línea mantiene la pausa más tiempo; [pause 2] mantiene la pausa sin nada nuevo).
    • Voz (cualquier TTS o una grabación, una toma por bloque, nunca editada): python engine/words.py take.wav words.json, luego compara la transcripción con el guión palabra por palabra. La imagen se ajusta a la toma, nunca al revés.
  4. ⛔ Fotogramas de storyboard. Construye los diseños de los bloques (solo las posiciones son suficientes), ejecuta check, luego render.py A.js words.json storyboard build/sb y storyboard_sheet.py build/storyboard.png build/sb. Revisa la hoja tú mismo (lista más abajo), luego envíala. Nada se anima antes de que el dueño apruebe el diseño.
  5. Construye el movimiento. Sincroniza cada elemento con la palabra que lo nombra (reglas más abajo). Ejecuta check después de cada cambio.
  6. Render. render.py A.js words.json video build/A.mp4 --workers 4 por bloque (nunca --draft para nada que se envíe). El build/A.mp4.render.json de cada bloque debe indicar "capture": "fresh" y el mismo navegador que los otros bloques; si el navegador cambió, vuelve a renderizar todos los bloques.
  7. Escaneo de fallos. faultscan.py build/A.mp4 --sheet build/A_contact.png en cada bloque, luego en el reel final. Revisa cada instante marcado. Una banda negra o un destello de un fotograma es un fallo: detente y corrígelo. El contenido oscuro de una foto es aceptable.
  8. Ensamblaje. assemble.py blocks.json <Reel>_REV01_<fecha>.mp4 [--music bed.mp3]. Imprime el volumen medido; debe estar en el objetivo (-14 LUFS por defecto) y el pico verdadero en o por debajo de -1.5 dBTP.
  9. ⛔ Revisión. Envía el reel y su hoja de contacto. Las correcciones solo de imagen vuelven a renderizar solo los bloques cambiados y se entregan como la siguiente _REV. El dueño lo publica.

Reglas de movimiento (por qué: una sensación tranquila y premium; nada tiembla, nada es difícil de leer)

  • Las palabras nunca se mueven. Una palabra aparece (0.28 s) y luego permanece quieta. Sin deslizamiento, escala, rebote ni deriva en el texto.
  • Titulares: hl(el, text, cue(...)) hace aparecer cada palabra en su propia palabra en la línea de tiempo. Cuando un titular parafrasea una voz, usa {stream:true}: las palabras aparecen de izquierda a derecha, con 0.1 s de separación, desde la señal.
  • Cámara: completamente quieta mientras se mantiene una toma; solo se desliza (0.8 s, ease-in-out) entre tomas. En 9:16, apila las tomas verticalmente en el mundo (toma 2 en y 1920, toma 3 en y 3840) y deslízate hacia abajo: SHOTS=[[t,540,960,1],[t2,540,2880,1],...].
  • Las fotos pueden acercarse lentamente: push:[1,1.04] = velocidad constante, nunca más de +6 % (MOTION.PUSH_MAX; el motor rechaza más). Sin push en una foto que lleva texto.
  • Los dibujos se dibujan a medida que se nombran, referencia primero: paredes antes de la ventana y la puerta en ellas, la línea del suelo antes de las alturas medidas desde ella. at('#walls', cue('Walls'), null, 'draw', 0.9); 0.6–1.0 s por elemento.
  • Dimensiones a una sola escala. Si un plano muestra dos dimensiones, ambas usan la misma escala.
  • Etiqueta lo que las palabras nombran; cada número nombra su unidad ("3.2 m", nunca un "3.2" desnudo).
  • Sin rebote, sobreimpulso, ease elástico ni de resorte en ningún lugar. Cada sincronización proviene de MOTION en reel.js; cámbialo ahí, nunca como literal en un bloque.
  • Un elemento SVG discontinuo desnudo dado a at() se muestra antes de su señal: envuélvelo en un <g>.

Tokens de sincronización (MOTION en reel.js)

TokenValorUso
WORD_FADE / WORD_LEAD0.28 s / 0.08 saparición de palabra; comienza justo antes de la palabra
STREAM0.10 sespaciado de palabras en titulares en modo stream
REVEAL / EXIT0.5 s / 0.4 sbloques, chips, muestras, fotos dentro / fuera
draw (at(...,'draw',dur))0.6–1.0 spor elemento dibujado
CAM_SLIDE / CAM_LEAD0.8 s / 0.35 sdeslizamiento de cámara; comienza antes de la señal de la toma
PUSH_MAX0.06límite de push de foto
WIPE1.2 sbarrido de antes/después
Easing: ease-out cúbico para revelados, ease-in-out cúbico para deslizamientos, dibujos y barridos.

Diseño y la comprobación

  • Zona segura (SAFE en reel.js): texto, etiquetas y subtítulos permanecen dentro de x 65–1015, y 269–1248 del fotograma 1080x1920 (14 % superior, 35 % inferior, 6 % laterales). Estas cifras son una guía ampliamente utilizada de terceros para Reels, no una regla publicada por Meta para publicaciones orgánicas: vuelve a medir desde una captura de pantalla de un reel publicado y establece safeZone({...}) si la aplicación cambia. Las fotos y dibujos pueden extenderse más allá; el texto no.
  • render.py <block> words.json check build/<block>.check.json es obligatorio antes de cada render. Muestrea cada 0.25 s y falla en cualquier superposición mantenida 0.5 s o más entre elementos separados (texto, dibujos, chips, muestras, fotos, subtítulos) y en cualquier texto fuera de la zona segura mientras la cámara se mantiene. Un fallo significa arreglar el diseño, nunca renderizar. Demuestra una nueva regla con una prueba negativa (reintroduce un fallo conocido; la comprobación debe fallar).
  • El texto que pertenece a una foto va dentro del elemento de la foto (parent: photoDiv) con un scrim() detrás, o la foto es un fondo (bg:true). De lo contrario, la comprobación informa texto sobre foto, que es lo que quieres.
  • La comprobación no ve cosas cortadas en el borde del fotograma: la autorevisión sí.

Construcción de bloques (reel.js)

boot(()=>{                                   // todo bloque comienza con boot(...)
  handle('@yourstudio');                     // identificador pequeño en la parte superior de la zona segura
  $('world').insertAdjacentHTML('beforeend', `<div id="h1" class="abs serif" style="left:90px;top:360px;width:900px;font-size:104px"></div>`);
  hl('h1', 'A small room, *re-planned.*', cue('A small'));          // *...* = acento cursiva; ~word = no está en la línea de tiempo
  at('#walls', cue('Walls'), null, 'draw', 0.9);                   // fx: 'fade' | 'pop' | 'wipe' | 'draw'
  photo({src:rel('photos/living.jpg'), x:0, y:1920, w:1080, h:1350, a:cue('Light'), push:[1,1.04]});
  beforeAfter({before:rel('photos/before.jpg'), after:rel('photos/after.jpg'), x:90, y:2440, w:900, h:900, a:cue('Before'), t:cue('after')});
  swatch({name:'Oak', note:'floor', color:'#B08A62', x:90, y:4480, size:270, a:cue('Oak')});   // o src: rel('photos/oak.jpg')
  clip({dir:rel('clips/walk'), n:240, x:0, y:5760, w:1080, h:1920, a:cue('Walk'), t0:cue('Walk')}); // fotogramas de frames.py
  captions(1120, 3);                         // subtítulos quemados de la línea de tiempo (reels con voz)
  SHOTS=[[0,540,960,1],[cue('Light'),540,2880,1]];
});

rel(path) resuelve un archivo junto al bloque. cue('words', after) lanza un error si las palabras no están en la línea de tiempo, por lo que un error tipográfico falla de forma notoria. Ejemplo completo: examples/demo/demo.js.

Reels en árabe y otros de derecha a izquierda

  • Llama a rtl() primero en boot(). Los titulares usan Noto Naskh Arabic, el texto IBM Plex Sans Arabic (OFL); el latín usa las fuentes predeterminadas. Sin cursiva, espaciado entre letras ni mayúsculas en árabe (las letras deben unirse); las palabras acentuadas cambian solo de color.
  • Los cuadros de texto mantienen left + width; el texto se alinea al borde derecho. Coloca las listas de derecha a izquierda (el primero nombrado = el más a la derecha).
  • Las señales ignoran diacríticos, tatweel, variantes de alef/yaa/taa-marbuta y convierten dígitos arábigo-indios; una señal sin letras da error. ة y ه cuentan como una letra, así que señala una frase más larga donde dos palabras difieren solo ahí. Las palabras numéricas coinciden en inglés solamente: en árabe, señala los dígitos o la palabra según se transcribe (words.py --lang ar elige el modelo multilingüe).
  • Los subtítulos se rompen en ، ؛ ؟ y puntuación latina, y no la muestran.
  • Ejemplo: examples/arabic/.

Aspecto

El tema vive solo en engine/templates/reel.css (colores como variables CSS, fuentes) y los archivos de fuentes. Valor por defecto "minimalista cálido": papel #F4EFE8, tinta #2B2622, acento #9C6B4E, titulares Cormorant Garamond + texto Jost (ambos SIL OFL 1.1). Para cambiar el aspecto: edita las variables, o añade fuentes OFL a engine/fonts/ con su archivo de licencia y enuméralas en fonts.css y window.FONTS (reel.js). Pide al dueño 3–5 capturas de pantalla de su cuadrícula primero y ajusta.

Autorevisión (Claude, antes de enviar cualquier hoja o render)

  1. Cada elemento comienza en su palabra; las líneas de referencia se dibujan primero.
  2. Las palabras permanecen quietas una vez colocadas; la cámara quieta mientras se mantiene; sin salto al final de un deslizamiento.
  3. Nada cortado en el borde del fotograma; todo el texto dentro de la zona segura; nada importante debajo del 35 % inferior.
  4. Dimensiones a una sola escala; cada número tiene su unidad; la ortografía coincide con la hoja de ritmos firmada.
  5. CHECK PASS en cada bloque; render.json dice capture: fresh, mismo navegador para todos los bloques.
  6. Escaneo de fallos limpio, o cada instante marcado inspeccionado y explicado.
  7. Volumen y pico verdadero impresos por assemble.py en el objetivo.

Derechos y privacidad

  • Solo las fotos del dueño, o aquellas de las que tiene los derechos. Casas de clientes: sin rostros, direcciones ni documentos sin consentimiento.
  • Música: solo pistas para las que el dueño tenga licencia, o la biblioteca propia de la plataforma añadida en la aplicación al publicar.
  • Sin afirmaciones que el dueño no haya hecho (precios, plazos, premios).

Solución de problemas

  • cue not found: "...": las palabras no están en words.json en ese orden; corrige la hoja de ritmos o la señal.
  • ERR reqfail:file:///...jpg: la ruta de una foto o clip es incorrecta; usa rel() y verifica el nombre del archivo.
  • Copias fantasma de dibujos anteriores o una banda negra: renderizaste con --draft. Vuelve a renderizar sin él.
  • Renders lentos: --workers 4 (hasta 8) en una máquina con esa cantidad de núcleos; comprobación y storyboard son rápidos, el video no.

Skills relacionados