Render videos programmatically from a dataset
You can use Remotion to do a batch render to create many videos based on a dataset. In the following example, we are going to turn a JSON dataset into a series of videos.
We'll start by creating a blank Remotion project:
- npm
- pnpm
- bun
- yarn
bash
npm init video --blank
bash
npm init video --blank
bash
pnpm create video --blank
bash
pnpm create video --blank
bash
bun create video --blank
bash
bun create video --blank
bash
yarn create video --blank
bash
yarn create video --blank
Sample dataset
JSON is the most convenient format to import in Remotion. If your dataset is in a different format, you can convert it using one of many available libraries on NPM.
my-data.tsts
export const data = [{name: "React",repo: "facebook/react",logo: "https://upload.wikimedia.org/wikipedia/commons/a/a7/React-icon.svg",},{name: "Remotion",repo: "remotion-dev/remotion",logo: "https://github.com/remotion-dev/logo/raw/main/withouttitle/element-0.png",},];
my-data.tsts
export const data = [{name: "React",repo: "facebook/react",logo: "https://upload.wikimedia.org/wikipedia/commons/a/a7/React-icon.svg",},{name: "Remotion",repo: "remotion-dev/remotion",logo: "https://github.com/remotion-dev/logo/raw/main/withouttitle/element-0.png",},];
Sample component
This component will animate a title, subtitle and image using Remotion. Replace the contents of the src/Composition.tsx
file with the following:
Composition.tsxtsx
import React from "react";import {AbsoluteFill,Img,interpolate,spring,useCurrentFrame,useVideoConfig,} from "remotion";type Props = {name: string;logo: string;repo: string;};export const MyComposition: React.FC<Props> = ({ name, repo, logo }) => {const frame = useCurrentFrame();const { fps } = useVideoConfig();const scale = spring({fps,frame: frame - 10,config: {damping: 100,},});const opacity = interpolate(frame, [30, 40], [0, 1], {extrapolateLeft: "clamp",extrapolateRight: "clamp",});const moveY = interpolate(frame, [20, 30], [10, 0], {extrapolateLeft: "clamp",extrapolateRight: "clamp",});return (<AbsoluteFillstyle={{scale: String(scale),backgroundColor: "white",fontWeight: "bold",justifyContent: "center",alignItems: "center",}}><divstyle={{display: "flex",flexDirection: "row",alignItems: "center",gap: 20,}}><Imgsrc={logo}style={{height: 80,}}/><divstyle={{display: "flex",flexDirection: "column",}}><divstyle={{fontSize: 40,transform: `translateY(${moveY}px)`,lineHeight: 1,}}>{name}</div><divstyle={{fontSize: 20,opacity,lineHeight: 1.25,}}>{repo}</div></div></div></AbsoluteFill>);};
Composition.tsxtsx
import React from "react";import {AbsoluteFill,Img,interpolate,spring,useCurrentFrame,useVideoConfig,} from "remotion";type Props = {name: string;logo: string;repo: string;};export const MyComposition: React.FC<Props> = ({ name, repo, logo }) => {const frame = useCurrentFrame();const { fps } = useVideoConfig();const scale = spring({fps,frame: frame - 10,config: {damping: 100,},});const opacity = interpolate(frame, [30, 40], [0, 1], {extrapolateLeft: "clamp",extrapolateRight: "clamp",});const moveY = interpolate(frame, [20, 30], [10, 0], {extrapolateLeft: "clamp",extrapolateRight: "clamp",});return (<AbsoluteFillstyle={{scale: String(scale),backgroundColor: "white",fontWeight: "bold",justifyContent: "center",alignItems: "center",}}><divstyle={{display: "flex",flexDirection: "row",alignItems: "center",gap: 20,}}><Imgsrc={logo}style={{height: 80,}}/><divstyle={{display: "flex",flexDirection: "column",}}><divstyle={{fontSize: 40,transform: `translateY(${moveY}px)`,lineHeight: 1,}}>{name}</div><divstyle={{fontSize: 20,opacity,lineHeight: 1.25,}}>{repo}</div></div></div></AbsoluteFill>);};
Writing the script
In order to render our videos, we'll first need to bundle our project using Webpack and prepare it for rendering.
This can be done by using the bundle()
function from the @remotion/bundler
package. Make sure to include the webpack override in the bundle if you have one.
ts
import {bundle } from "@remotion/bundler";import {webpackOverride } from "./webpack-override";constbundleLocation = awaitbundle ({entryPoint : "./src/index.ts",// If you have a webpack override, don't forget to add itwebpackOverride :webpackOverride ,});
ts
import {bundle } from "@remotion/bundler";import {webpackOverride } from "./webpack-override";constbundleLocation = awaitbundle ({entryPoint : "./src/index.ts",// If you have a webpack override, don't forget to add itwebpackOverride :webpackOverride ,});
Rendering videos
Import the dataset and loop over each entry.
Fetch the composition metadata for each render using selectComposition()
. You have the opportunity to parametrize the duration, width and height of the composition based on the data entry with the calculateMetadata()
function.
Trigger a render using renderMedia()
and pass the data entry as inputProps
. This will pass the object as React props to the component above.
ts
import {renderMedia ,selectComposition } from "@remotion/renderer";import {data } from "./dataset";for (constentry ofdata ) {constcomposition = awaitselectComposition ({serveUrl :bundleLocation ,id :compositionId ,inputProps :entry ,});awaitrenderMedia ({composition ,serveUrl :bundleLocation ,codec : "h264",outputLocation : `out/${entry .name }.mp4`,inputProps :entry ,});}
ts
import {renderMedia ,selectComposition } from "@remotion/renderer";import {data } from "./dataset";for (constentry ofdata ) {constcomposition = awaitselectComposition ({serveUrl :bundleLocation ,id :compositionId ,inputProps :entry ,});awaitrenderMedia ({composition ,serveUrl :bundleLocation ,codec : "h264",outputLocation : `out/${entry .name }.mp4`,inputProps :entry ,});}
It is not recommended to render more than one video at once.
Full script
render.mjsts
import {selectComposition ,renderMedia } from "@remotion/renderer";import {webpackOverride } from "./webpack-override";import {bundle } from "@remotion/bundler";import {data } from "./dataset";constcompositionId = "MyComp";constbundleLocation = awaitbundle ({entryPoint : "./src/index.ts",// If you have a webpack override, don't forget to add itwebpackOverride :webpackOverride ,});for (constentry ofdata ) {constcomposition = awaitselectComposition ({serveUrl :bundleLocation ,id :compositionId ,inputProps :entry ,});awaitrenderMedia ({composition ,serveUrl :bundleLocation ,codec : "h264",outputLocation : `out/${entry .name }.mp4`,inputProps :entry ,});}
render.mjsts
import {selectComposition ,renderMedia } from "@remotion/renderer";import {webpackOverride } from "./webpack-override";import {bundle } from "@remotion/bundler";import {data } from "./dataset";constcompositionId = "MyComp";constbundleLocation = awaitbundle ({entryPoint : "./src/index.ts",// If you have a webpack override, don't forget to add itwebpackOverride :webpackOverride ,});for (constentry ofdata ) {constcomposition = awaitselectComposition ({serveUrl :bundleLocation ,id :compositionId ,inputProps :entry ,});awaitrenderMedia ({composition ,serveUrl :bundleLocation ,codec : "h264",outputLocation : `out/${entry .name }.mp4`,inputProps :entry ,});}
Running the script
- Node
- Bun
bash
node render.mjs
bash
node render.mjs
To use TypeScript, rename the file to render.ts
, install ts-node
from npm and run ts-node render.ts
. If you get errors, wrap the asynchronous code in an async function and call it.
bash
bun render.mjs
bash
bun render.mjs
TypeScript should work out of the box if you rename your file to render.ts
.
Rendering videos from a CSV dataset
Use a package like csv2json
to convert your dataset into a JSON.