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:

Terminal
git clone https://github.com/green-screen-memes/green-screen-memes-api
cd green-screen-memes-api/examples/remotion
npm install

Or add Remotion to a project of your own (these are the versions the example pins):

Terminal
npm i remotion@4.0.538 @remotion/cli@4.0.538 react@19.2.9 react-dom@19.2.9

You 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.

Terminal
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.json

Look 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:

Terminal
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.webm
  • format=yuva420p and -pix_fmt yuva420p keep the transparency; -auto-alt-ref 0 is required for libvpx to write it.
  • For a clip whose chromaKeyColor is blue or red, use 0x0000ff or 0xff0000. For none, drop the chromakey.
  • If a green fringe stays, raise 0.3 a 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.

src/Meme.tsx
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:

src/Root.tsx
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))};
    }}
  />
);
src/index.ts
import {registerRoot} from 'remotion';
import {RemotionRoot} from './Root';

registerRoot(RemotionRoot);

5Preview and render

Terminal
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.

Get a free key