Video de producto en Remotion
Una demo de producto sin rostro (formato 3 de short-video-formats) construida como código. La voz marca el ritmo: cada línea del guión se habla, las tomas se colocan una tras otra, y las escenas y subtítulos del video siguen esas marcas de tiempo. La UI se reconstruye en React a partir de los estilos propios del producto, por lo que se ve nítida a cualquier nivel de zoom y se vuelve a renderizar cuando el producto o el guión cambian. Todo excepto el MP4 renderizado vive en Git, por lo que cualquier versión anterior está a un checkout y un render de distancia.
Entradas: Node 22, ffmpeg, un TTS (edge-tts para borradores; una voz con licencia antes de publicar), los tokens CSS y el markup del producto (la demo del producto en una landing page suele ser la mejor fuente), y el guión.
Flujo de trabajo
1. Configuración
npx create-video@latest --blank reel && cd reel && npm i @remotion/google-fonts
Mantener un theme.ts con el marco (1080×1920, 30 fps), las bandas del diseño y los tokens de color del producto copiados de su CSS. Nuestras bandas: la banda del título centrada en y=118, la banda blanca de UI desde y=220 durante 1250 px (65% de la altura, ancho completo), los subtítulos centrados en y=1640.
2. Escribir el guión como datos
script.json, una entrada por línea de subtítulo; *estrellas* marcan las palabras destacadas:
{ "voice": { "name": "en-US-AndrewMultilingualNeural", "rate": "+10%" }, "gap": 0.16,
"lines": [
{ "scene": "wall", "text": "Dejé de *promptear* a Claude Code." },
{ "scene": "wall", "text": "Lo *contraté*.", "pause": 0.5 },
{ "scene": "end", "text": "Contrata a tu primer *empleado IA*.", "say": "Crewmark. Contrata a tu primer empleado IA." }
],
"post": { "tiktok": { "caption": "…", "hashtags": [] }, "instagram": { "caption": "…", "hashtags": [] } } }
say es para cuando la voz difiere del subtítulo; pause mantiene una escena más tiempo después de una línea.
3. Voz → línea de tiempo
Un pequeño script de Node vocaliza cada línea en su propio WAV, recorta el silencio, las coloca una tras otra con gap/pause entre ellas, las mezcla en public/voice.wav (adelay + amix, luego loudnorm=I=-14 para volumen social), y escribe public/timeline.json con los from/to de cada línea en fotogramas:
{ "durationInFrames": 959, "lines": [ { "scene": "wall", "text": "Dejé de *promptear* a Claude Code.", "from": 12, "to": 51 } ] }
Almacenar en caché cada toma por su texto y voz para que editar una línea no vuelva a vocalizar el resto. La durationInFrames de la composición proviene de este archivo.
4. Las escenas siguen la línea de tiempo
Agrupar líneas consecutivas con la misma scene en un <Sequence>; dar a cada escena sus desplazamientos de línea ("beats") para que las animaciones caigan en las palabras. Los subtítulos son un componente que lee la línea actual.
La pieza clave es una cámara sobre la banda de UI: colocar la UI en píxeles CSS reales, luego escalarla y desplazarla.
export function Cam({ zoom, x = 0, y = 0, children }: { zoom: number; x?: number; y?: number; children: React.ReactNode }) {
return (
<div style={{ position: "absolute", inset: 0, overflow: "hidden", background: "#fff" }}>
<div style={{ position: "absolute", width: 1080 / zoom + x, height: STAGE_H / zoom + y,
transform: `scale(${zoom}) translate(${-x}px, ${-y}px)`, transformOrigin: "0 0" }}>{children}</div>
</div>
);
}
Con zoom = 1080 / 520, un diseño de 520 px de ancho (dos tarjetas de 248 px) llena el ancho exactamente. Animar zoom, x e y con interpolación suavizada entre beats para acercarse al botón que se está presionando.
Ideas de escenas que funcionan bien: un mercado de tarjetas, un formulario que se llena solo (texto escrito con cursor), un tablero donde una tarjeta se mueve de columna, un hilo de actividad, un gráfico que se dibuja solo (etiquetado "Datos demo"), un chat donde el agente pregunta y tú apruebas, una tarjeta final al estilo del héroe de la landing page.
5. Renderizar y revisarlo
node voice.mjs && npx tsc --noEmit
npx remotion still src/index.ts Reel /tmp/f0.png --frame=0 # la portada
npx remotion render src/index.ts Reel out/reel.mp4
ffmpeg -v error -y -i out/reel.mp4 -vf fps=1,scale=200:-1,tile=8x5 -frames:v 1 out/sheet.jpg
Mirar la hoja de contacto y un fotograma de cada escena: la banda de UI completa, nada cortado en la parte inferior de la banda, subtítulos dentro de la franja inferior, la escena correcta bajo cada línea, el fotograma 0 no en blanco.
6. Versionarlo
Hacer commit del código fuente, el guión y voice.wav/timeline.json; mantener out/ y las descargas en .gitignore. Para recuperar una versión anterior, hacer checkout de ese commit y renderizar de nuevo.
Lista de verificación
- Colores, fuentes y texto tomados del CSS y texto propios del producto
- Banda de UI ≥ 50% de la altura, ancho completo; franjas solo encima y debajo
- Fotograma 0 renderizado y verificado como portada
- Cada escena muestra una característica que el producto realmente tiene
- Números demo etiquetados
- Hoja de contacto revisada después de cada render
- Código fuente y voz commiteados; MP4 no
Errores que encontramos
- Fundido de entrada en la primera escena dejó el fotograma 0, la portada, en blanco. Omitir la animación de entrada para la primera escena.
- Contenido cortado por la cámara. Un gráfico dimensionado para un nivel de zoom se salió del borde inferior de la banda en el zoom de la escena. Verificar un fotograma fijo al final de cada movimiento de cámara, no solo al inicio.
- Mitades inferiores vacías. Con zoom bajo, las escenas cortas dejaban la parte inferior de la banda blanca vacía. Acercar más o anclar algo al fondo (un campo de mensaje, un pie de página) para que la banda parezca una pantalla real.
- Un guión de revisión que necesitaba un .srt. La herramienta de revisión de nuestro antiguo pipeline de ffmpeg leía tiempos de subtítulos que Remotion no escribe. Una hoja de contacto con
fps=1,tilehace el mismo trabajo. transformduplicado en un solo objeto de estilo (una animación de presión más una animación de entrada) falla la verificación de tipos. Envolver el elemento y poner una transformación en cada capa.- Edge TTS para el corte final. Es gratuito y sirve para borradores, pero suena robótico junto a cualquier cosa que parezca humana, y no tiene licencia comercial. Cambiar la función TTS antes de publicar a escala.
Habilidades relacionadas
- short-video-formats: reglas de diseño y qué formato construir.
- social-publish: el texto de la publicación que se guarda en
script.json. - video-retro: qué cambiar en el próximo render.