Remotion (Video Programático)
Remotion renderiza componentes React cuadro por cuadro. Cada cuadro es una función pura del número de cuadro, por lo que la animación es determinista y se puede volver a renderizar.
La API de Remotion cambia entre versiones principales. Lee la versión instalada (
npm ls remotion) y la documentación oficial en remotion.dev antes de usar una API de la que no estés seguro. Revisa los términos de licencia de Remotion para uso comercial por parte de empresas.
Configuración
npx create-video@latest # elige la plantilla en blanco (TypeScript)
cd my-video && npm install
npx remotion studio # vista previa en vivo
npx remotion render MiComp out/video.mp4
npx remotion still MiComp out/thumb.png --frame=45
El modelo mental
useCurrentFrame()devuelve el cuadro actual, comenzando en 0.useVideoConfig()proporcionafps,width,height,durationInFrames.- Nada se anima por sí solo. Calculas el estilo a partir del cuadro.
- Nunca uses transiciones CSS,
setTimeout,Math.random()oDate.now()para animar. Rompen la determinismo del renderizado. Usarandom('seed')de remotion en su lugar.
Composición
// src/Root.tsx
import {Composition} from 'remotion';
import {Explicativo} from './Explicativo';
export const RaizRemotion = () => (
<Composition
id="Explicativo"
component={Explicativo}
durationInFrames={30 * 20} // 20 s a 30 fps
fps={30}
width={1920}
height={1080}
defaultProps={{title: 'Hola'}}
/>
);
Primitivas de animación
import {AbsoluteFill, Sequence, interpolate, spring, useCurrentFrame, useVideoConfig} from 'remotion';
export const Titulo: React.FC<{texto: string}> = ({texto}) => {
const frame = useCurrentFrame();
const {fps} = useVideoConfig();
const opacidad = interpolate(frame, [0, 15], [0, 1], {extrapolateRight: 'clamp'});
const escala = spring({frame, fps, config: {damping: 200}}); // suave, sin rebote
return (
<AbsoluteFill style={{justifyContent: 'center', alignItems: 'center'}}>
<h1 style={{opacity: opacidad, transform: `scale(${0.9 + 0.1 * escala})`, fontSize: 96}}>{texto}</h1>
</AbsoluteFill>
);
};
export const Explicativo = () => (
<AbsoluteFill style={{backgroundColor: '#0b1020', color: 'white'}}>
<Sequence from={0} durationInFrames={90}><Titulo text="Introducción" /></Sequence>
<Sequence from={90} durationInFrames={120}><Titulo text="Punto principal" /></Sequence>
</AbsoluteFill>
);
Siempre pasa extrapolateLeft/Right: 'clamp' a interpolate a menos que quieras que los valores se salgan del rango.
Medios
import {staticFile, Audio, OffthreadVideo, Img} from 'remotion';
<Audio src={staticFile('voz.mp3')} />
<Audio src={staticFile('musica.mp3')} volume={0.12} />
<OffthreadVideo src={staticFile('demo.mp4')} />
<Img src={staticFile('logo.png')} />
Coloca los activos en public/ y refiérelos con staticFile(). Usa <Img> en lugar de <img> para que el renderizado espere la imagen. Las versiones más nuevas de Remotion proporcionan componentes de medios actualizados; consulta la documentación para la versión instalada.
Sincronizar escenas con narración
No codifiques las duraciones de las escenas. Derívalas del audio.
- Genera archivos de voz por escena (
voiceover-tts). - Mide cada duración con
ffprobey escribescenes.jsoncon{id, audio, seconds}. - En la composición, convierte con
Math.ceil((seconds + 0.5) * fps)y coloca<Sequence>por desplazamiento acumulado. - Usa
calculateMetadataen el<Composition>para establecer eldurationInFramestotal a partir de esos datos.
Si la voz se vuelve a grabar, el video se reajusta solo.
Transiciones
Instala @remotion/transitions y usa TransitionSeries con fade(), slide(), wipe(). Una transición superpone sus dos escenas, por lo que la longitud total se reduce en la duración de la transición. Mantén las transiciones de 12 a 20 cuadros a 30 fps.
Fuentes y marca
Carga fuentes con @remotion/google-fonts o @remotion/fonts para que estén listas antes del primer cuadro. Mantén colores, fuentes y logotipo en un solo brand.ts para que un cambio de marca sea una modificación de un solo archivo.
Rendimiento y renderizado
- Previsualiza en el estudio; renderiza desde la CLI.
--concurrency=50%limita el uso de CPU.--codec=h264es el predeterminado; usa--codec=prorespara entrega a edición.- El desenfoque intenso, las sombras y los filtros SVG grandes son lentos. Prefiere activos pre-renderizados para esos casos.
- Usa
<OffthreadVideo>en lugar de<video>para material de origen y obtener extracción precisa por cuadro. - Para borradores rápidos:
--scale=0.5y un rango--frames=0-90.
Errores comunes
| Error | Solución |
|---|---|
| Parpadeo o imagen faltante al renderizar | Usa <Img>, staticFile y delayRender para datos asíncronos |
| La animación difiere entre la vista previa y el renderizado | Elimina código no determinista (time, random) |
| El texto se desborda en diferentes configuraciones regionales | Define anchos explícitos; prueba la cadena más larga |
| Video demasiado largo después de agregar transiciones | Resta los cuadros de transición del total |
| El audio está silencioso en el renderizado | Verifica la ruta del archivo y que el <Audio> esté dentro de un <Sequence> activo |
Combínalo con motion-design-craft para elegir tiempos y easing.