Guide
Green screen memes in Remotion
To put a green screen meme in a Remotion video, key it once with FFmpeg into a WebM with a transparent background, then play it with <OffthreadVideo transparent> over whatever you like. Preview and render match, renders stay fast, and the clip keeps its sound.
This guide builds a 1080×1920 composition: a background image, a meme clip from the Green Screen Memes API at the bottom, and a caption. It was rendered with Remotion 4.0.538 and FFmpeg 7.1.
1Set up a Remotion project
The quickest start is the example project, which has every file below:
git clone https://github.com/green-screen-memes/green-screen-memes-api
cd green-screen-memes-api/examples/remotion
npm installOr add Remotion to a project of your own (these are the versions the example pins):
npm i remotion@4.0.538 @remotion/cli@4.0.538 react@19.2.9 react-dom@19.2.9You also need FFmpeg built with libvpx (most builds are) and a free API key in GSM_API_KEY.
2Fetch a clip from the API
Search by the reaction you want, download the first match into public/, and save its durationSeconds so the composition can match the clip's length. Copy in your background image too.
mkdir -p public
cp ~/Pictures/background.jpg public/background.jpg
curl -fsS -G -H "Authorization: Bearer $GSM_API_KEY" \
--data-urlencode "q=side eye" --data-urlencode "limit=3" \
https://api.greenscreenmemes.app/v1/memes > results.json
curl -fsSL -o public/greenscreen.mp4 "$(jq -r '.data[0].videoUrl' results.json)"
jq '.data[0] | {id, name, durationSeconds}' results.json > public/clip.jsonLook at results.json first if you want to choose: each result has a name, a thumbnailUrl and its chromaKeyColor. The example repo does this step in Node, with no dependencies (fetch-clip.mjs).
Media links are signed and expire after about 6 hours, so download the clip into public/ instead of pointing the composition at its URL.
3Pre-key it to a transparent WebM
One FFmpeg pass removes the green and writes VP9 with an alpha channel, using the same chromakey settings as the FFmpeg guide:
ffmpeg -i public/greenscreen.mp4 \
-vf "chromakey=0x00ff00:0.3:0.08,format=yuva420p" \
-c:v libvpx-vp9 -pix_fmt yuva420p -auto-alt-ref 0 -b:v 0 -crf 30 -row-mt 1 \
-c:a libopus -b:a 128k public/greenscreen.webmformat=yuva420pand-pix_fmt yuva420pkeep the transparency;-auto-alt-ref 0is required for libvpx to write it.- For a clip whose
chromaKeyColorisblueorred, use0x0000ffor0xff0000. Fornone, drop thechromakey. - If a green fringe stays, raise
0.3a little; if the subject loses color, lower it.
4Write the composition
The background fills the frame, and the keyed clip sits at the bottom at full width. transparent makes Remotion extract the clip's frames with their alpha channel when it renders.
import {AbsoluteFill, Img, OffthreadVideo, staticFile} from 'remotion';
export type MemeProps = {
/** An image in public/, shown full-bleed behind the clip. */
background: string;
/** The clip, already keyed to a transparent WebM. */
clip: string;
/** Optional caption at the top. */
caption: string;
};
export const Meme: React.FC<MemeProps> = ({background, clip, caption}) => (
<AbsoluteFill style={{backgroundColor: 'black'}}>
<Img src={staticFile(background)} style={{width: '100%', height: '100%', objectFit: 'cover'}} />
{/* Clip at the bottom, full width, with its own sound. `transparent` keeps the
alpha channel when Remotion extracts frames during a render. */}
<AbsoluteFill style={{justifyContent: 'flex-end'}}>
<OffthreadVideo src={staticFile(clip)} transparent style={{width: '100%'}} />
</AbsoluteFill>
{caption ? (
<AbsoluteFill style={{alignItems: 'center', paddingTop: 200, paddingLeft: 60, paddingRight: 60}}>
<div
style={{
background: 'rgba(0, 0, 0, 0.85)',
color: 'white',
fontFamily: 'Helvetica, Arial, sans-serif',
fontSize: 64,
fontWeight: 800,
lineHeight: 1.15,
padding: '14px 28px',
borderRadius: 18,
textAlign: 'center',
}}
>
{caption}
</div>
</AbsoluteFill>
) : null}
</AbsoluteFill>
);The composition reads clip.json to last exactly as long as the clip:
import {Composition, staticFile} from 'remotion';
import {Meme, type MemeProps} from './Meme';
const FPS = 30;
/** Written next to the keyed clip: the API's id, name and durationSeconds. */
type ClipInfo = {id: string; name: string; durationSeconds: number | null};
export const RemotionRoot: React.FC = () => (
<Composition
id="Meme"
component={Meme}
width={1080}
height={1920}
fps={FPS}
durationInFrames={FPS * 5}
defaultProps={{background: 'background.jpg', clip: 'greenscreen.webm', caption: ''} satisfies MemeProps}
// The video lasts as long as the clip.
calculateMetadata={async () => {
const info: ClipInfo = await fetch(staticFile('clip.json')).then(r => r.json());
return {durationInFrames: Math.max(1, Math.round((info.durationSeconds ?? 5) * FPS))};
}}
/>
);import {registerRoot} from 'remotion';
import {RemotionRoot} from './Root';
registerRoot(RemotionRoot);5Preview and render
npx remotion studio src/index.ts # preview in the browser
npx remotion render src/index.ts Meme out/meme.mp4 \
--props='{"background":"background.jpg","clip":"greenscreen.webm","caption":"when the invoice arrives"}'The result is a 1080×1920 MP4 with the clip's sound, ready for Shorts, Reels or TikTok. Pass a different caption, background or clip in --props to render variations from a script.
Why pre-key instead of keying every frame
- Same result everywhere. Chrome plays VP9 with alpha natively in the Studio, and the renderer extracts the frames with alpha, so the preview matches the render.
- Fast renders. No per-pixel JavaScript on every frame.
- One place to tune. The key lives in one FFmpeg command, the same one the API's recipe uses.
The trade-off is one FFmpeg step before rendering. If you'd rather key live, recent Remotion versions (4.0.472 and later) have a colorKey() effect; see Remotion's green screen docs.
Before you publish
These are internet memes: Green Screen Memes makes the green screen versions, but the original footage isn't ours and isn't licensed with the clips, so how you use one is up to you. On Starter and up you can publish videos with them, for yourself or clients. Don't put clips of identifiable people in paid ads, sponsored posts or endorsements; animals, objects and cartoons are fine there. The terms have the details.
The full project is in the examples on GitHub. An AI agent can do the fetching for you over MCP.
Get a free key
Sandbox: 50 video downloads and 30,000 calls a month, for building and testing.