Video Animation Generator with Remotion

Build a customizable video overlay system and render 3 animation styles.

Introduction

30 Second Summary

You have a great video on your phone, but it looks like raw footage. What if you could layer on animated titles, accent graphics, and styled overlays to make it look polished, all without touching a traditional video editor?

In this project, you will build a video animation generator that takes any video from your camera roll, overlays customizable animated graphics using Remotion, and renders 3 completely different style variations as MP4 files. You will use Claude Code to write all the animation code from natural language prompts, then upload your favorite version to Google Drive.

What You'll Build

A Remotion project that layers animated text, accent graphics, and style badges on top of your own video, with 3 switchable animation presets and a Node.js upload script for Google Drive.

By the end of this project, you'll have:

  • A working Remotion project that overlays customizable animated text and graphics on top of your own video.
  • 3 rendered MP4 videos with different animation styles (energetic, minimal, elegant) to compare side by side.
  • A Google Drive upload script that sends your favorite version straight to the cloud.
  • Secret Mission: animated lower-third captions that slide in at timed intervals throughout your video.

Are there any prerequisites?

You need a Mac running macOS 15 (Sequoia) or later, since Remotion's renderer requires it. You also need a Claude Pro ($20/mo) or Max ($100-200/mo) subscription to use Claude Code. A free Google account is required for the Google Drive upload step.

Install Node.js and Claude Code

Before you can build anything with Remotion, you need two tools installed on your Mac.

The first is Node.js, the JavaScript runtime that powers Remotion's rendering engine. The second is Claude Code, an AI coding agent that will write your Remotion code from natural language prompts.

In this step, get ready to:
  • Install Node.js using Homebrew.
  • Install and verify Claude Code.
  • Authenticate Claude Code with your Pro or Max account.
Install Node.js

Remotion requires Node.js 16 or later to run. You will install it using Homebrew, a package manager for macOS.

  • Press Cmd+Space (macOS) to open your search bar.
  • Type Terminal and press Enter to open it.
  • Check if you already have Homebrew by running this command:
brew --version

I see a version number

Homebrew is ready. Skip ahead to installing Node.js below.

Command not found

  • Install Homebrew by running this command:
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"

What does this command do?

This downloads and runs the official Homebrew installer script from GitHub. It will ask for your Mac password during installation.

The installer will print instructions when it finishes. Follow any steps it shows to add Homebrew to your PATH.

  • Install Node.js by running this command:
brew install node
  • Verify the installation by running these commands:
node --version
npm --version

You should see version numbers printed for both. Any version of Node.js 16 or later works.

Don't see a version number?

  • If you see "command not found", try closing and reopening your terminal. Homebrew sometimes needs a fresh session to register new commands.
  • If Homebrew itself is not working, check that you followed any post-install instructions it printed (especially the PATH export lines).

Help me troubleshoot my Node.js installation on macOS

Install Claude Code

Claude Code is a terminal-based AI agent that can read, write, and edit files in your project. Remotion has official support for Claude Code through its Skills system, which teaches the agent how to write Remotion-specific code.

Native installer (recommended)

  • Install Claude Code by running this command:
curl -fsSL https://claude.ai/install.sh | bash

What does this command do?

This installs the Claude Code binary to ~/.local/bin/claude. The native installer also enables automatic background updates, so you always have the latest version.

If you see "command not found" after installing, the install directory is not in your PATH yet.

  • Fix this by running these commands:
echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.zshrc
source ~/.zshrc

Homebrew

  • Install Claude Code via Homebrew by running this command:
brew install --cask claude-code

Pro tip: no auto-updates

Homebrew installations do not auto-update. Run brew upgrade claude-code periodically to stay current.

  • Verify Claude Code is installed by running:
claude --version

You should see a version number printed in your terminal.

Still seeing command not found?

  • Close your terminal completely and reopen it. The PATH changes only take effect in a new session.
  • If you used the native installer, make sure ~/.local/bin is in your PATH by running echo $PATH and checking the output.

Help me fix my Claude Code installation

Authenticate Claude Code

Claude Code needs to connect to your Claude Pro or Max account before it can generate code for you.

  • Start Claude Code by running:
claude

A browser window opens automatically for you to log in.

  • Sign in with the account that has your Claude Pro or Max subscription.
  • After logging in, return to your terminal. You should see Claude Code's interactive prompt ready for input.
  • Type exit and press Enter to close Claude Code for now.

Why exit now?

Your credentials are stored after the first login. You will not need to log in again. In the next step, you will start Claude Code inside your project directory so it has access to your files.

Browser not opening?

  • Check if your terminal printed a URL. Copy and paste it into your browser manually.
  • Make sure you have an active Claude Pro ($20/mo) or Max ($100-200/mo) subscription. Claude Code does not have a free tier.

Help me authenticate Claude Code

Your development environment is ready. Next up, you will scaffold a Remotion project, drop in your video, and get Claude Code running inside it.

Create Your Remotion Project and Add a Video

Your development environment is ready with Node.js and Claude Code installed. Now it's time to create the actual project that will power your video animations.

Remotion lets you create videos using React components. In this step, you'll scaffold a new Remotion project, drop in your source video, and get both the live preview and Claude Code running side by side.

In this step, get ready to:
  • Scaffold a new Remotion project with Claude Code Skills.
  • Add your source video to the project.
  • Start the Remotion Studio preview and Claude Code in parallel.
Scaffold the Remotion project

Remotion provides a scaffolding tool that creates a ready-to-go video project. It also offers an option to install "Skills" that teach Claude Code how to write Remotion code correctly.

  • Move to your Desktop by running this command:
cd ~/Desktop
  • Scaffold your Remotion project by running this command. It launches an interactive wizard that asks several questions about your project setup:
npx create-video@latest

Seeing a prompt to install create-video?

If this is your first time running this command, npx asks permission to download the scaffolder package. Press y and then Enter to continue.

The wizard asks a series of questions. Answer each one as listed below:

  • Enter video-animator for the project name.
  • Select Blank as the template.
  • Enter yes for the TailwindCSS prompt.
  • Enter yes for the Install Skills prompt.

What are Remotion Skills?

Skills are instruction files that get placed in a .claude/skills/ directory inside your project. They teach Claude Code about Remotion's APIs, best practices, and component patterns. This is Remotion's official recommended workflow for AI-assisted video creation.

  • Navigate into your new project and install its dependencies by running these commands:
cd video-animator
npm install

What does this do?

  • cd video-animator moves your terminal into the project folder the scaffolder just created.
  • npm install downloads all the packages Remotion needs (React, the video renderer, TailwindCSS, and more) into a node_modules/ folder.
  • Wait for the install to finish. You should see a message like added X packages when it completes.

Seeing permission errors or install failures?

  • Check your Node.js version by running node --version. Remotion requires Node.js 16 or later.
  • If you see EACCES permission errors, your npm global directory may need a permissions fix. Try prefixing the command with sudo or reinstall Node.js via Homebrew (which avoids permission issues).
  • Make sure you are inside the video-animator folder before running npm install.

Help me troubleshoot my npm install error in my Remotion project

Add your source video

You need a short video to use as the base layer for your animations. Pick something from your phone's camera roll (5 to 15 seconds works best) or record a quick clip.

  • Transfer your video to your Mac. AirDrop is the fastest way if you're using an iPhone.
  • Copy your video into the project's public/ folder by running a command like this:
cp ~/Downloads/my-video.mp4 public/my-video.mp4

Pro tip: adjust the paths

Replace ~/Downloads/my-video.mp4 with the actual path to your video file. If you used AirDrop, it likely landed in your Downloads folder. Use whatever filename your video has.

Why the public/ folder?

Remotion serves files from the public/ folder as static assets. You reference them in code using staticFile('my-video.mp4'). Both .mp4 and .webm formats work.

  • Confirm the file is in the right place by running:
ls public/
  • You should see your video file listed in the output. Note the exact filename. You'll reference it when prompting Claude Code in the next step.

Don't see your video file?

  • Make sure your terminal is inside the video-animator folder. Run pwd to check.
  • Double-check the source path in your cp command. If the file is not in ~/Downloads/, find it in Finder and drag it into the public/ folder manually.

Help me find and copy my video file into the Remotion public folder

Start the Remotion preview and Claude Code

You need two processes running at the same time: the Remotion Studio preview (so you can see your animations live) and Claude Code (so you can prompt it to write Remotion code). Each one gets its own terminal window.

  • In your current terminal, start the Remotion Studio preview by running:
npm run dev

Remotion Studio opens in your browser at http://localhost:3000. You should see a blank canvas with a timeline at the bottom. This is where you'll preview your animations in real time.

Browser didn't open automatically?

  • Open your browser manually and go to http://localhost:3000. If port 3000 is taken, check your terminal output for the actual URL.
  • If you see an error in the terminal instead of a running server, make sure you ran npm install successfully in the previous substep.

Help me get Remotion Studio running in my browser

  • Open a second terminal tab by pressing Cmd+T.
  • Navigate into your project and start Claude Code by running these commands:
cd ~/Desktop/video-animator
claude

What does this do?

Starting Claude Code inside your project directory means it can see all your files, including the Remotion Skills you installed during scaffolding. It knows how to write Remotion code using the correct APIs and patterns.

You now have two terminals running. The first runs Remotion Studio for live previews. The second runs Claude Code for prompting.

Your project is scaffolded, your video is in place, and both Remotion Studio and Claude Code are running. Next up, you'll prompt Claude Code to build the animation overlay system with 3 distinct visual presets.

Build the Animation System with Claude Code

Your Remotion project is scaffolded and your source video is sitting in the public/ folder.

Now it is time to build the animation system that layers text and graphics on top of your video. You will prompt Claude Code to generate three files: a presets configuration, a VideoOverlay component, and an updated Root file. Each prompt describes what you want, and Claude Code writes the Remotion code for you.

In this step, get ready to:
  • Create an animation presets config with 3 distinct visual styles.
  • Build a VideoOverlay component that layers animated text on your video.
  • Register 3 compositions in Remotion and preview each animation style.
Create the animation presets config

A preset defines the visual identity for one animation style. It controls colors, font sizes, animation type, and overlay opacity. By keeping these values in a separate config file, you can swap entire visual styles without touching the component code.

  • In your Claude Code terminal, create the presets file by entering this prompt:
Create a file src/presets.ts that defines 3 animation presets for video overlays. Each preset should have: a name, a color palette (primary, secondary, accent, textColor), an animation type (spring, smooth, or fade), title and subtitle font sizes, font family, and overlay opacity. The 3 presets should be:
1. "energetic" - bright cyan/magenta/yellow colors, spring animations, large bold text, Arial font
2. "minimal" - white/gray monochrome, fade animations, smaller text, Helvetica Neue font
3. "elegant" - gold and cream on dark, smooth interpolate animations, serif Georgia font
Export the presets object and the AnimationPreset type.

What does the presets file define?

  • Each preset has a name, a color palette (primary, secondary, accent, textColor), and an animationType that controls how text appears on screen.
  • The energetic preset uses bright cyan and magenta with spring animations for a bouncy feel.
  • The minimal preset uses white and gray with fade animations for a clean, understated look.
  • The elegant preset uses gold and cream on dark with smooth interpolation for a refined, cinematic appearance.
  • Open src/presets.ts in your editor to review the generated file.
  • You should see a presets object with three entries (energetic, minimal, elegant) and an exported AnimationPreset type.

Claude Code generated something different?

  • If a preset is missing properties like overlayOpacity or fontFamily, tell Claude Code: "Add the missing properties to each preset in presets.ts" and list what is missing.
  • If the file uses different property names than what you expected, check the double-check tab below to compare against the reference code.
  • help me fix my presets.ts file so it matches the expected structure

Awesome, I've got everything!

Double check you have saved src/presets.ts before moving on.

I'd like to double check the full code

Your src/presets.ts should look like this:

export type AnimationPreset = {
  name: string;
  colors: {
    primary: string;
    secondary: string;
    accent: string;
    textColor: string;
  };
  animationType: 'spring' | 'smooth' | 'fade';
  titleFontSize: number;
  subtitleFontSize: number;
  fontFamily: string;
  overlayOpacity: number;
};

export const presets: Record<string, AnimationPreset> = {
  energetic: {
    name: 'Energetic',
    colors: {
      primary: '#00D4FF',
      secondary: '#FF006E',
      accent: '#FFD600',
      textColor: '#FFFFFF',
    },
    animationType: 'spring',
    titleFontSize: 80,
    subtitleFontSize: 36,
    fontFamily: 'Arial, sans-serif',
    overlayOpacity: 0.3,
  },
  minimal: {
    name: 'Minimal',
    colors: {
      primary: '#FFFFFF',
      secondary: '#AAAAAA',
      accent: '#FFFFFF',
      textColor: '#FFFFFF',
    },
    animationType: 'fade',
    titleFontSize: 56,
    subtitleFontSize: 24,
    fontFamily: 'Helvetica Neue, sans-serif',
    overlayOpacity: 0.15,
  },
  elegant: {
    name: 'Elegant',
    colors: {
      primary: '#D4AF37',
      secondary: '#1A1A2E',
      accent: '#F5E6CC',
      textColor: '#F5E6CC',
    },
    animationType: 'smooth',
    titleFontSize: 64,
    subtitleFontSize: 28,
    fontFamily: 'Georgia, serif',
    overlayOpacity: 0.4,
  },
};
Create the VideoOverlay component

The VideoOverlay component is the main composition that brings everything together. It takes your video, layers a gradient on top, and animates a title, subtitle, accent line, and style label badge. The animation behavior changes based on which preset you pass in.

  • In your Claude Code terminal, create the VideoOverlay component by entering this prompt:
Create a file src/VideoOverlay.tsx that is a Remotion composition component. It should:
1. Accept props: videoFile (string), titleText (string), subtitleText (string), and preset (AnimationPreset from ./presets)
2. Display the video as a full-screen background using OffthreadVideo with staticFile()
3. Add a gradient overlay at the bottom using the preset's secondary color
4. Animate the title text appearing based on the preset's animationType: spring uses Remotion's spring(), smooth uses interpolate over 45 frames, fade uses interpolate over 30 frames
5. Animate the subtitle appearing 15 frames after the title
6. Add an accent line that grows from 0 to 200px width using interpolate
7. Show a small style label badge that fades in at frame 60 using a Sequence
Use the preset's colors, fonts, and sizes for all styling. Add text shadows for readability over video.

What does VideoOverlay do?

  • OffthreadVideo displays your video as a full-screen background. It extracts frames server-side using FFmpeg for frame-perfect rendering.
  • The gradient overlay uses the preset's secondary color to darken the bottom of the frame. This makes overlaid text readable against any video.
  • The title animation switches between spring() (bouncy physics), interpolate() over 45 frames (smooth slide), or interpolate() over 30 frames (fade) depending on the preset's animationType.
  • A Sequence component delays the style label badge until frame 60, so it fades in after the title and subtitle have settled.
  • Open src/VideoOverlay.tsx in your editor to confirm the file was created.
  • You should see imports from remotion (including OffthreadVideo, spring, interpolate, Sequence) and the AnimationPreset type from ./presets.

You will not see the component in Remotion Studio yet. The next substep registers it as a composition so the preview can render it.

File is missing or has errors?

  • If src/VideoOverlay.tsx does not exist, Claude Code may have used a different filename. Check your src/ folder for any new .tsx file and rename it if needed.
  • If the component is missing OffthreadVideo or uses Video instead, tell Claude Code: "Replace the Video component with OffthreadVideo from remotion."
  • help me fix my VideoOverlay component

Awesome, I've got everything!

Double check you have saved src/VideoOverlay.tsx before moving on.

I'd like to double check the full code

Your src/VideoOverlay.tsx should look like this:

import React from 'react';
import {
  AbsoluteFill,
  OffthreadVideo,
  staticFile,
  useCurrentFrame,
  useVideoConfig,
  interpolate,
  spring,
  Sequence,
} from 'remotion';
import {AnimationPreset} from './presets';

type VideoOverlayProps = {
  videoFile: string;
  titleText: string;
  subtitleText: string;
  preset: AnimationPreset;
};

export const VideoOverlay: React.FC<VideoOverlayProps> = ({
  videoFile,
  titleText,
  subtitleText,
  preset,
}) => {
  const frame = useCurrentFrame();
  const {fps} = useVideoConfig();

  const titleProgress = (() => {
    switch (preset.animationType) {
      case 'spring':
        return spring({frame, fps, config: {damping: 12, stiffness: 100}});
      case 'smooth':
        return interpolate(frame, [0, 45], [0, 1], {
          extrapolateRight: 'clamp',
        });
      case 'fade':
      default:
        return interpolate(frame, [0, 30], [0, 1], {
          extrapolateRight: 'clamp',
        });
    }
  })();

  const subtitleProgress = (() => {
    const delay = 15;
    switch (preset.animationType) {
      case 'spring':
        return spring({
          frame: Math.max(0, frame - delay),
          fps,
          config: {damping: 12, stiffness: 100},
        });
      case 'smooth':
        return interpolate(frame, [20, 65], [0, 1], {
          extrapolateLeft: 'clamp',
          extrapolateRight: 'clamp',
        });
      case 'fade':
      default:
        return interpolate(frame, [15, 45], [0, 1], {
          extrapolateLeft: 'clamp',
          extrapolateRight: 'clamp',
        });
    }
  })();

  const titleTranslateY =
    preset.animationType === 'spring'
      ? interpolate(titleProgress, [0, 1], [40, 0])
      : 0;

  const accentLineWidth = interpolate(frame, [10, 50], [0, 200], {
    extrapolateLeft: 'clamp',
    extrapolateRight: 'clamp',
  });

  return (
    <AbsoluteFill>
      <AbsoluteFill>
        <OffthreadVideo
          src={staticFile(videoFile)}
          style={{width: '100%', height: '100%', objectFit: 'cover'}}
        />
      </AbsoluteFill>

      <AbsoluteFill
        style={{
          background: `linear-gradient(to top, ${preset.colors.secondary}CC, transparent 60%)`,
          opacity: preset.overlayOpacity + 0.3,
        }}
      />

      <AbsoluteFill
        style={{
          justifyContent: 'flex-end',
          alignItems: 'flex-start',
          padding: 60,
          paddingBottom: 80,
        }}
      >
        <div
          style={{
            width: accentLineWidth,
            height: 4,
            backgroundColor: preset.colors.primary,
            marginBottom: 20,
            borderRadius: 2,
          }}
        />

        <div
          style={{
            fontSize: preset.titleFontSize,
            fontFamily: preset.fontFamily,
            fontWeight: 'bold',
            color: preset.colors.textColor,
            opacity: titleProgress,
            transform: `translateY(${titleTranslateY}px)`,
            textShadow: '0 2px 10px rgba(0,0,0,0.5)',
            lineHeight: 1.1,
          }}
        >
          {titleText}
        </div>

        <div
          style={{
            fontSize: preset.subtitleFontSize,
            fontFamily: preset.fontFamily,
            color: preset.colors.accent,
            opacity: subtitleProgress,
            marginTop: 12,
            textShadow: '0 1px 6px rgba(0,0,0,0.4)',
          }}
        >
          {subtitleText}
        </div>

        <Sequence from={60}>
          <div
            style={{
              marginTop: 24,
              padding: '8px 20px',
              backgroundColor: `${preset.colors.primary}33`,
              borderLeft: `3px solid ${preset.colors.primary}`,
              borderRadius: 4,
              opacity: interpolate(
                Math.max(0, frame - 60),
                [0, 20],
                [0, 1],
                {
                  extrapolateRight: 'clamp',
                },
              ),
            }}
          >
            <span
              style={{
                fontSize: 18,
                color: preset.colors.textColor,
                fontFamily: preset.fontFamily,
              }}
            >
              {preset.name} Style
            </span>
          </div>
        </Sequence>
      </AbsoluteFill>
    </AbsoluteFill>
  );
};
Register 3 compositions and preview them

Remotion uses compositions to define renderable video units. Each composition has an ID, dimensions, frame rate, and default props. By registering 3 compositions with different presets, you get 3 distinct versions of the same video from one codebase.

  • In your Claude Code terminal, update the Root file by entering this prompt:
Update src/Root.tsx to register 3 Composition components: "energetic", "minimal", and "elegant". Each should use the VideoOverlay component with the matching preset from presets.ts. Use these shared settings: durationInFrames 150, fps 30, width 1080, height 1920 (portrait video). Set defaultProps with videoFile "my-video.mp4", titleText "My Video", subtitleText "Created with Remotion". Import presets from ./presets.

What do these compositions do?

  • Each Composition registers a renderable video with Remotion. The id (e.g. "energetic") becomes the name you use to render or preview that specific version.
  • durationInFrames: 150 at fps: 30 gives you a 5-second video. Adjust durationInFrames to match your video's length (multiply seconds by 30).
  • The dimensions 1080x1920 are portrait (phone video). If your video is landscape, swap to 1920x1080.
  • defaultProps provides the video filename, title text, subtitle text, and preset for each composition.
  • If your video filename is different from my-video.mp4, tell Claude Code: "Change the videoFile in defaultProps to your-actual-filename.mp4 for all 3 compositions."
  • Check Remotion Studio in your browser at http://localhost:3000.

You should see 3 compositions listed in the left sidebar: energetic, minimal, and elegant. Click each one to preview the different animation styles playing over your video.

Pro tip: customize your presets

If any preset does not look right, tell Claude Code what to change in natural language. For example: "Make the energetic preset's title bounce more" or "Change the elegant preset's gold color to be more warm." Claude Code can iterate on the styling until you are happy with it.

Don't see 3 compositions in the sidebar?

  • Make sure the Remotion Studio dev server is still running. If you stopped it, run npm run dev again in your project folder.
  • Check that src/Root.tsx imports both VideoOverlay and presets. If either import is missing, Claude Code may have created the file in a different location.
  • If you see a red error screen in the browser, read the error message. It often points to a missing import or a typo in a filename.
  • help me debug why my Remotion compositions are not showing up

Awesome, I've got everything!

Double check that all 3 files are saved: src/presets.ts, src/VideoOverlay.tsx, and src/Root.tsx.

I'd like to double check the full code

Your src/Root.tsx should look like this:

import React from 'react';
import {Composition} from 'remotion';
import {VideoOverlay} from './VideoOverlay';
import {presets} from './presets';

export const RemotionRoot: React.FC = () => {
  return (
    <>
      <Composition
        id="energetic"
        component={VideoOverlay}
        durationInFrames={150}
        fps={30}
        width={1080}
        height={1920}
        defaultProps={{
          videoFile: 'my-video.mp4',
          titleText: 'My Video',
          subtitleText: 'Created with Remotion',
          preset: presets.energetic,
        }}
      />
      <Composition
        id="minimal"
        component={VideoOverlay}
        durationInFrames={150}
        fps={30}
        width={1080}
        height={1920}
        defaultProps={{
          videoFile: 'my-video.mp4',
          titleText: 'My Video',
          subtitleText: 'Created with Remotion',
          preset: presets.minimal,
        }}
      />
      <Composition
        id="elegant"
        component={VideoOverlay}
        durationInFrames={150}
        fps={30}
        width={1080}
        height={1920}
        defaultProps={{
          videoFile: 'my-video.mp4',
          titleText: 'My Video',
          subtitleText: 'Created with Remotion',
          preset: presets.elegant,
        }}
      />
    </>
  );
};

Your animation system is built and you can preview all 3 styles in real time. Next up, you will render all 3 compositions as MP4 files so you can compare them side by side.

Render All 3 Video Versions

You have a working animation system with 3 distinct visual presets. Each one transforms your video with different colors, typography, and animation styles.

Previewing in Remotion Studio is great for development, but you need actual MP4 files to share your videos. In this step, you will create a shell script that renders all 3 compositions in one go.

In this step, get ready to:
  • Create a shell script that renders all 3 compositions as MP4 files.
  • Run the render and verify the output.
  • Compare all 3 video versions and pick your favorite.
Create the render script

Remotion's CLI renders one composition at a time using npx remotion render. To render all 3 presets in sequence, you will create a shell script that calls this command three times.

  • Create a file called render-all.sh in the root of your video-animator folder with this content:
#!/bin/bash
echo "Rendering 3 video versions..."
echo ""

echo "1/3 Rendering energetic version..."
npx remotion render src/index.ts energetic out/energetic.mp4
echo "Done: out/energetic.mp4"
echo ""

echo "2/3 Rendering minimal version..."
npx remotion render src/index.ts minimal out/minimal.mp4
echo "Done: out/minimal.mp4"
echo ""

echo "3/3 Rendering elegant version..."
npx remotion render src/index.ts elegant out/elegant.mp4
echo "Done: out/elegant.mp4"
echo ""

echo "All 3 versions rendered! Check the out/ folder."

What does this script do?

  • Each npx remotion render line takes 3 arguments: the entry file (src/index.ts), the composition ID (matching the id you registered in Root.tsx), and the output file path.
  • The script renders all 3 compositions one after another: energetic, minimal, and elegant.
  • Output files land in the out/ folder, which Remotion creates automatically.
  • Save render-all.sh.
  • Make the script executable by running this command:
chmod +x render-all.sh

Awesome, I've got everything!

Double check you have saved render-all.sh and made it executable.

I'd like to double check the full code

Here is the complete render-all.sh file for comparison.

#!/bin/bash
echo "Rendering 3 video versions..."
echo ""

echo "1/3 Rendering energetic version..."
npx remotion render src/index.ts energetic out/energetic.mp4
echo "Done: out/energetic.mp4"
echo ""

echo "2/3 Rendering minimal version..."
npx remotion render src/index.ts minimal out/minimal.mp4
echo "Done: out/minimal.mp4"
echo ""

echo "3/3 Rendering elegant version..."
npx remotion render src/index.ts elegant out/elegant.mp4
echo "Done: out/elegant.mp4"
echo ""

echo "All 3 versions rendered! Check the out/ folder."
Run the render

Remotion's renderer and the Studio preview both use the same bundler. You need to stop the preview server before rendering.

  • Switch to the terminal where Remotion Studio is running.
  • Press Ctrl+C to stop the preview server.
  • Run the render script by running this command:
./render-all.sh

What should I see?

Each composition displays a progress bar as it renders. The total time depends on your video length and your Mac's processing power. A 5-second video typically takes 1-2 minutes per composition.

  • Confirm the 3 files were created by running:
ls out/

You should see energetic.mp4, minimal.mp4, and elegant.mp4 in the output.

Render failing?

  • Seeing Error: No composition with ID? Make sure the composition IDs in Root.tsx match exactly: energetic, minimal, and elegant.
  • Seeing ENOENT or a file-not-found error for your video? Check that your video file is in the public/ folder and the filename in Root.tsx matches exactly (including the file extension).
  • Seeing Permission denied? Run chmod +x render-all.sh to make the script executable, then try again.
  • Help me debug my render-all.sh script
Review your 3 versions
  • Open the out/ folder by running:
open out/

You will see energetic.mp4, minimal.mp4, and elegant.mp4. Play each one in QuickTime or your preferred video player.

Compare how each preset transforms the same source video. The energetic version uses bold bouncy animations with cyan and magenta. The minimal version keeps things clean and monochrome. The elegant version features warm gold tones with smooth motion.

  • Pick your favorite version. You will upload it to Google Drive in the next step.

Pro tip: tweak and re-render

If a preset is close but not quite right, ask Claude Code to adjust it. For example: "Make the energetic preset's title bounce more" or "Change the elegant preset's gold to a warmer tone." Then run ./render-all.sh again to re-render with your changes.

Your 3 video versions are ready to share. Next up, you will set up Google Drive access and upload your favorite version straight from the terminal.

Upload Your Favorite Video to Google Drive

You now have 3 rendered MP4 videos sitting in your out/ folder, each with a completely different animation style. Time to pick a winner and share it.

In this step, you will set up a Google Cloud service account, write a Node.js upload script, and push your favorite video straight to Google Drive.

In this step, get ready to:
  • Set up a Google Cloud service account with access to the Google Drive API.
  • Create a Node.js upload script that pushes a video file to a shared Drive folder.
  • Upload your favorite rendered video and verify it appears in Google Drive.
Set up Google Cloud credentials

Your upload script needs permission to write files to Google Drive on your behalf. Instead of logging in through a browser every time, you will create a service account that acts as a dedicated identity for your script.

What is a service account?

A service account is a special Google account that belongs to your application, not to a person. It has its own email address and credentials. Your script authenticates with a JSON key file instead of a username and password.

The tradeoff is that files uploaded by a service account are owned by that account. To see them in your personal Google Drive, you share a folder with the service account's email address.

New to Google Cloud

  • Go to console.cloud.google.com and sign in with your Google account.
  • Click Select a project at the top of the page.
  • Click New Project.
  • Enter video-animator as the project name.
  • Click Create.
  • Type Google Drive API in the search bar at the top of the page.
  • Click on Google Drive API from the search results.
  • Click Enable.
  • In the left sidebar, click IAM & Admin.
  • Click Service Accounts.
  • Click Create Service Account.
  • Enter video-uploader as the service account name.
  • Click Create and Continue.
  • Skip the optional role and access steps by clicking Done.
  • Click on the video-uploader service account you just created.
  • Go to the Keys tab.
  • Click Add Key, then select Create New Key.
  • Select JSON as the key type.
  • Click Create.

A JSON key file downloads to your computer automatically.

  • Move the key file into your project by running this command:
mv ~/Downloads/*.json service-account.json

What if I have other JSON files in Downloads?

The wildcard *.json grabs all JSON files. If you have other JSON files in your Downloads folder, replace *.json with the actual filename. It looks something like video-animator-abc123-xxxxxxxxxxxx.json.

Now you need to share a Google Drive folder with your service account so uploaded files are visible to you.

  • Go back to the Service Accounts page and copy the service account's email address. It looks like video-uploader@video-animator-XXXXX.iam.gserviceaccount.com.
  • Open Google Drive in your browser.
  • Create a new folder called Rendered Videos.
  • Right-click the folder and click Share.
  • Paste the service account email address into the sharing field.
  • Set the permission to Editor.
  • Click Send.
  • Open the Rendered Videos folder.
  • Look at the URL in your browser's address bar. Copy the folder ID. It is the long string of characters after /folders/ in the URL. Save this somewhere. You will need it in the next substep.

Already Have Google Cloud

  • Open your existing project at console.cloud.google.com.
  • Enable the Google Drive API if it is not already enabled.
  • Go to IAM & Admin, then Service Accounts.
  • Create a new service account called video-uploader.
  • Create a JSON key for it. Move the downloaded file into your project root and rename it to service-account.json.
  • In Google Drive, create or pick a folder and share it with the service account email. Give it Editor access.
  • Open the folder in Drive and copy the folder ID from the URL. It is the string after /folders/.

Pro tip: protect your credentials

The service-account.json file contains credentials that grant access to your Google Cloud project. Do not commit it to Git or share it publicly. Add it to your .gitignore file now if you plan to use version control.

Create the upload script

With credentials in place, you can write the script that talks to the Google Drive API. The script reads your video file from disk, authenticates with the service account key, and uploads the file to your shared folder.

  • Install the Google APIs client library by running this command:
npm install googleapis@171

You should see googleapis added to the dependencies in your terminal output.

npm install failing?

  • Make sure you are inside the video-animator folder. Run pwd to check your current directory.
  • Check your internet connection. npm needs to download the package from the npm registry.

Why can't I install googleapis?

  • Create a file called upload-to-drive.mjs in your project root. Copy the following code into it:
// Import Google APIs client and Node.js file utilities
import {google} from 'googleapis';
import fs from 'fs';
import path from 'path';

// Path to the service account key file in your project root
const SERVICE_ACCOUNT_KEY = path.join(process.cwd(), 'service-account.json');
// Scope limits access to only files this app creates
const SCOPES = ['https://www.googleapis.com/auth/drive.file'];

// Replace this with your Google Drive folder ID
const FOLDER_ID = '[[DRIVE_FOLDER_ID="your Google Drive folder ID"]]';

// Authenticate using the service account credentials
const auth = new google.auth.GoogleAuth({
  keyFile: SERVICE_ACCOUNT_KEY,
  scopes: SCOPES,
});

// Create a Drive API client instance
const drive = google.drive({version: 'v3', auth});

// Read the file path from the command line argument
const filePath = process.argv[2];

// Exit with a usage message if no file was specified
if (!filePath) {
  console.error('Usage: node upload-to-drive.mjs <path-to-video>');
  process.exit(1);
}

const fileName = path.basename(filePath);

console.log(`Uploading ${fileName} to Google Drive...`);

// Upload the video file to the specified Drive folder
const response = await drive.files.create({
  requestBody: {
    name: fileName,
    mimeType: 'video/mp4',
    parents: [FOLDER_ID],
  },
  media: {
    mimeType: 'video/mp4',
    body: fs.createReadStream(filePath),
  },
  fields: 'id, name, webViewLink',
});

// Print the uploaded file details and shareable link
console.log(`Uploaded: ${response.data.name}`);
console.log(`File ID: ${response.data.id}`);
console.log(`View: ${response.data.webViewLink}`);

What does this code do?

  • The GoogleAuth object reads your service account key file and authenticates with Google's servers.
  • The drive.file scope means the script can only manage files it creates. It cannot read or delete other files in your Drive.
  • The drive.files.create call streams the video file directly from disk. It does not load the entire file into memory first.
  • The parents: [FOLDER_ID] setting places the uploaded file inside your shared Drive folder so you can see it.
  • Replace the FOLDER_ID placeholder with the folder ID you copied from Google Drive in the previous substep.
  • Save upload-to-drive.mjs.

Not sure where to find the folder ID?

Open the shared folder in Google Drive. Look at the URL in your browser. It looks like drive.google.com/drive/folders/1aBcDeFgHiJkLmNoPqRsTuVwXyZ. The folder ID is the long string after /folders/.

How do I find my Google Drive folder ID?

Awesome, I've got everything!

Make sure you have saved upload-to-drive.mjs with your actual folder ID filled in.

I'd like to double check the full code

Here is the complete upload-to-drive.mjs for comparison. Make sure your folder ID is filled in on the FOLDER_ID line.

import {google} from 'googleapis';
import fs from 'fs';
import path from 'path';

const SERVICE_ACCOUNT_KEY = path.join(process.cwd(), 'service-account.json');
const SCOPES = ['https://www.googleapis.com/auth/drive.file'];

const FOLDER_ID = 'your-folder-id-here';

const auth = new google.auth.GoogleAuth({
  keyFile: SERVICE_ACCOUNT_KEY,
  scopes: SCOPES,
});

const drive = google.drive({version: 'v3', auth});

const filePath = process.argv[2];

if (!filePath) {
  console.error('Usage: node upload-to-drive.mjs <path-to-video>');
  process.exit(1);
}

const fileName = path.basename(filePath);

console.log(`Uploading ${fileName} to Google Drive...`);

const response = await drive.files.create({
  requestBody: {
    name: fileName,
    mimeType: 'video/mp4',
    parents: [FOLDER_ID],
  },
  media: {
    mimeType: 'video/mp4',
    body: fs.createReadStream(filePath),
  },
  fields: 'id, name, webViewLink',
});

console.log(`Uploaded: ${response.data.name}`);
console.log(`File ID: ${response.data.id}`);
console.log(`View: ${response.data.webViewLink}`);
Upload your favorite video

Everything is wired up. Time to send your favorite rendered video to Google Drive.

  • Run the upload script with the path to your favorite video. For example, to upload the elegant version:
node upload-to-drive.mjs out/elegant.mp4

Which file should I upload?

Replace elegant.mp4 with whichever version you liked best. Your options are out/energetic.mp4, out/minimal.mp4, or out/elegant.mp4.

You should see three lines printed in your terminal: the uploaded file name, a file ID, and a view link.

  • Open the view link in your browser to confirm the video is in your Google Drive folder.

Seeing an authentication or permission error?

  • Make sure service-account.json is in your project root (the same folder where you run the command). Run ls service-account.json to verify.
  • Check that the FOLDER_ID in your script matches the folder you shared with the service account email.
  • Confirm the service account has Editor access on the Drive folder. Open the folder in Google Drive, click Share, and check the permission.
  • Make sure you enabled the Google Drive API in your Google Cloud project. Go back to the Cloud Console and search for it to confirm.

Help me debug my Google Drive upload error.

Secret mission

Add Animated Lower-Third Captions

Your overlay system handles titles and accent graphics, but professional videos use timed lower-third captions to narrate what is happening on screen. Can you build a reusable LowerThird component that slides in, holds, and slides out at specific moments in your video?

Before you go

Clean Up Your Resources

Decide whether to keep your resources running, pause them to come back later, or delete them entirely. This project runs locally with no cloud compute costs, but your Claude Code subscription has an ongoing monthly fee.

Cost warning

Your Claude Code subscription costs $20/mo (Pro) or $100-200/mo (Max). If you no longer need it, manage your subscription at claude.ai. The Google Cloud service account and local files have no ongoing cost.

Resources you used:

  • Remotion project folder (video-animator/) with source code and dependencies.
  • Rendered MP4 video files in the out/ folder.
  • Google Cloud service account (video-uploader) and JSON key file (service-account.json).
  • Google Drive shared folder with uploaded video.
  • Claude Code subscription (Pro or Max plan).

Keep everything running

No action needed. Choose this if you plan to keep building on your Remotion project or want to reuse the Google Drive upload script for future videos.

  • Add service-account.json and out/ to your .gitignore file if you plan to push this project to GitHub. The service account key file contains sensitive credentials that should never be committed to version control.
  • Your Remotion project and Google Cloud service account are reusable for future video projects.

Pause - I'll come back to this later

Shut down any running processes to free up memory. Your project files stay on disk and are ready to pick up again anytime.

  • Stop the Remotion Studio preview if it is still running (press Ctrl+C in its terminal).
  • Exit Claude Code by typing exit in the Claude Code terminal.
  • Add service-account.json and out/ to your .gitignore before committing anything to version control.
  • Your project folder, rendered videos, and Google Cloud service account will all be waiting when you come back.

Delete - I don't want to use this again

Remove all project resources and start fresh. Follow these steps to clean everything up.

  • Delete the Google Cloud service account. Go to the Google Cloud Console, navigate to IAM & Admin, then Service Accounts. Find the video-uploader service account and delete it.
  • Optionally delete the Google Cloud project entirely. In the Cloud Console, go to Settings and click Shut down to remove the video-animator project.
  • Remove the uploaded video from Google Drive. Open the Rendered Videos folder and delete the files.
  • Delete the local project folder from your computer.
rm -rf video-animator

Mission accomplished

That's a Wrap!

Nice work! You just built a full video animation pipeline from scratch using Claude Code and Remotion. You went from a raw camera roll clip all the way to 3 rendered MP4s with completely different visual styles, then uploaded your favorite to Google Drive.

You learned how to:

  • Build a Remotion project that layers animated text, accent graphics, and style labels on top of your own video using React components.
  • Create 3 animation presets (energetic, minimal, elegant) and render all 3 as MP4 files using a single shell script.
  • Upload videos to Google Drive programmatically using a service account and the Google Drive API.
  • Add timed lower-third captions that slide in and out at specific moments throughout the video.

Ready to quiz yourself?