Build a TikTok Daily Quotes AI Agent

Automate TikTok content by chaining quotes, AI, and video APIs.

Introduction

30 Second Summary

Content creators spend hours every day filming, editing, and posting to social media. What if a single script could handle the entire process in seconds?

In this project, you will build a Python AI agent that automates TikTok content creation from start to finish. It fetches a daily inspirational quote, generates a styled vertical video, crafts an AI-powered caption with hashtags, and posts everything to TikTok in one script execution.

What You'll Build

Run a single command and watch your agent pull today's quote, render a polished 1080x1920 video with a gradient background, generate a caption with trending hashtags, and upload the finished video to your TikTok account.

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

  • A one-command pipeline that fetches a daily quote, renders a vertical video, and posts it to TikTok without any manual steps.
  • An AI caption generator powered by Gemini that writes engaging, hashtag-filled captions tailored for TikTok audiences.
  • A video rendering engine that creates professional 1080x1920 videos with text overlays and gradient backgrounds using MoviePy and FFmpeg.
  • Secret Mission: Schedule the agent to run automatically every day with cron so you never touch it again.

Are there any prerequisites?

You should have basic Python experience with functions, imports, and HTTP requests. No video editing or AI experience is needed.

Before We Start

Before diving into code, take a moment to lock in what you're about to build and why it matters.

Set Up Your Tools

To build an AI agent that creates and posts videos, you need several tools working together. FFmpeg handles video encoding, Python packages power video creation and AI, and API keys connect your script to external services.

In this step, you will install everything your agent needs so that the rest of the project is pure Python coding.

In this step, get ready to:
  • Install FFmpeg and Python packages for video generation.
  • Get a Gemini API key for AI-powered captions.
  • Register on TikTok for Developers to enable video posting.
Install FFmpeg and Python packages

Your agent relies on FFmpeg to encode video files and several Python libraries to generate content and call APIs. Let's confirm your Python version first, then install FFmpeg, and finally set up the Python packages.

  • Open your terminal. Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
  • Type Terminal (macOS) or PowerShell (Windows) and press Enter to open it.

The google-genai SDK requires Python 3.10 or higher. Check your version by running:

python3 --version

✔️ I see version 3.10 or higher

Your Python version is ready. Move on to the FFmpeg installation below.

ⓧ I see a version lower than 3.10

You need Python 3.10+ for this project. On macOS, the default python3 often resolves to an older system version even after installing a newer one.

  • Visit python.org/downloads and download the latest Python 3.10+ installer for your OS.
  • Run the installer and follow the prompts.
  • After installing, try python3.12 --version (or whichever version you installed) to confirm it works. Use that specific binary name for the rest of this project when a command says python3.

ⓧ Command not found

Python is not installed on your system.

  • Visit python.org/downloads and download the latest Python 3.10+ installer for your OS.
  • Run the installer. On Windows, make sure to check Add Python to PATH during installation.
  • Close and reopen your terminal, then run python3 --version again.

Now install FFmpeg. This is the engine that encodes your quote videos into MP4 files.

macOS

  • Install FFmpeg using Homebrew by running:
brew install ffmpeg

Don't have Homebrew?

If you see "command not found: brew", install Homebrew first by following the instructions at brew.sh. Then re-run the command above.

Windows

  • Go to ffmpeg.org/download.html and find the Windows EXE Files section.
  • Click Windows builds from gyan.dev.
  • Download the ffmpeg-release-essentials.zip file.
  • Extract the zip file to a location like C:\ffmpeg.
  • Add the bin folder (e.g., C:\ffmpeg\bin) to your system PATH.
  • Open Settings, search for environment variables, click Edit the system environment variables.
  • Click Environment Variables, find Path under System variables, click Edit, and add the bin folder path.
  • Close and reopen your terminal after updating PATH.

Linux

  • Install FFmpeg using your package manager by running:
sudo apt-get install ffmpeg
  • Verify FFmpeg is installed by running:
ffmpeg -version

You should see version information and a list of enabled libraries printed to your terminal.

Seeing command not found?

If you just installed FFmpeg and still see an error, close your terminal completely and open a new one. The PATH updates only take effect in new terminal sessions.

On Windows, make sure the folder you added to PATH contains ffmpeg.exe directly (not a subfolder). Help me troubleshoot FFmpeg installation

Now set up a project folder with a virtual environment.

  • Move to your Desktop by running this command:
cd ~/Desktop
  • Create your project folder and move into it by running these commands:
mkdir tiktok-quotes-agent
cd tiktok-quotes-agent
  • Create and activate a virtual environment by running:

macOS

python3 -m venv venv
source venv/bin/activate

Windows

python -m venv venv
venv\Scripts\activate

Linux

python3 -m venv venv
source venv/bin/activate

Your terminal prompt now shows (venv) at the beginning, confirming the virtual environment is active.

  • Create a requirements.txt file that lists all the packages your agent needs. Open your code editor and create a new file called requirements.txt inside the tiktok-quotes-agent folder with this content:
moviepy==2.2.1
google-genai==2.16.0
requests==2.32.3
Pillow==11.2.1
python-dotenv==1.1.0

What are these packages?

  • moviepy generates and composites the video files.
  • google-genai is the SDK for calling Gemini to generate captions.
  • requests handles HTTP calls to the ZenQuotes and TikTok APIs.
  • Pillow creates the gradient background image for the video.
  • python-dotenv loads your API keys from a .env file so they stay out of your code.
  • Install all the packages by running:
pip install -r requirements.txt

What will I see during installation?

This command downloads and installs all five packages plus their dependencies. You will see lines like "Downloading..." and "Successfully installed" as each one completes. MoviePy is the largest and may take a minute.

  • Verify MoviePy can import correctly by running:
python -c "from moviepy import ColorClip; print('MoviePy OK')"

You should see MoviePy OK printed to your terminal.

Seeing an import error?

Make sure your virtual environment is active (you see (venv) in your prompt). If not, run the activate command again.

If you see an error about FFmpeg not being found, MoviePy depends on FFmpeg being available on your PATH. Re-check the FFmpeg installation above. Help me fix my MoviePy import error

Get your Gemini API key

Your agent uses Gemini to craft engaging TikTok captions with hashtags. To call the Gemini API, you need an API key from Google AI Studio.

  • Open aistudio.google.com in your browser.
  • Sign in with your Google account.
  • Click Get API key in the left sidebar.
  • Click Create API key.
  • Select any Google Cloud project when prompted (or let it create one for you).
  • Copy the generated API key. You will only see it once.

Is the Gemini API free?

Yes. The Gemini free tier provides free input and output tokens. Your agent makes roughly one request per run, which is well within any free-tier limit.

Now store your key in a .env file so your Python code can read it without hardcoding it.

  • Create a new file called .env in the tiktok-quotes-agent folder with this content:
GEMINI_API_KEY=your-gemini-api-key-here
TIKTOK_ACCESS_TOKEN=your-tiktok-access-token-here
  • Replace your-gemini-api-key-here with the API key you just copied from Google AI Studio.
  • Leave your-tiktok-access-token-here as a placeholder for now. You will fill it in after setting up TikTok.
  • Save the file.

Keep your .env file private

Never commit .env to Git or share it publicly. It contains your API credentials.

Register on TikTok for Developers

The final piece is connecting your agent to TikTok's Content Posting API so it can upload videos automatically. You will register a developer app that gives your script permission to post on your behalf.

  • Go to developers.tiktok.com and sign in with your TikTok account (or create one).
  • Click Manage apps from your developer dashboard.
  • Click Connect an app to create a new application.
  • Give your app a name (e.g., "Daily Quotes Agent") and fill in the required details.
  • Under Add products, find and add the Content Posting API product.
  • Note your Client key and Client secret from the app settings page. You will need these later for OAuth2 authentication.

What about app review?

TikTok requires app review before your app can post publicly. Unaudited apps can only post videos visible to yourself (SELF_ONLY privacy level).

You can build and test the entire pipeline right now without waiting for approval. Your videos will post as private until the review is complete.

What is OAuth2?

OAuth2 is an authentication standard that lets your script act on behalf of your TikTok account without sharing your password. You will implement the token exchange in Step 5.

Before you run the final verification, think about what you expect to see. If FFmpeg and MoviePy are both installed correctly, what should each command output?

  • Run the two verification commands to confirm everything is ready:
ffmpeg -version
python -c "from moviepy import ColorClip; print('MoviePy OK')"

The first command prints FFmpeg's version and build configuration. The second prints MoviePy OK. Both succeeding means your tools are ready to generate videos.

✔️ Both commands succeeded

Your environment is fully set up. You have FFmpeg for encoding, MoviePy for video generation, Gemini for AI captions, and TikTok API access for posting.

ⓧ One or both failed

  • If ffmpeg -version fails, revisit the FFmpeg installation section above and make sure you opened a fresh terminal after installing.
  • If the MoviePy import fails, confirm your virtual environment is active ((venv) in prompt) and re-run pip install -r requirements.txt.
  • Help me troubleshoot my setup

Your environment is ready to go. Next up, you will write the Python code that fetches a daily quote and turns it into a TikTok video.

Fetch a Quote and Generate a Basic Video

Your tools are installed and your API keys are stored safely. Now it's time to build the core of your agent: a script that fetches a quote and turns it into a video file you can actually play.

Before worrying about style or AI captions, you need proof that the pipeline works end to end. In this step, you will pull today's inspirational quote from the ZenQuotes API and render it as a vertical TikTok-ready video with MoviePy.

In this step, get ready to:
  • Fetch the daily quote of the day from ZenQuotes.
  • Generate a 1080x1920 vertical video with the quote as white text on a dark background.
  • Run the script and confirm the output video plays correctly.
Fetch the daily quote from ZenQuotes

The ZenQuotes API provides a free endpoint that returns the quote of the day as JSON. No API key is required. Your script will call https://zenquotes.io/api/today and parse out the quote text and author name.

  • Create a new file called agent.py in your project folder by running:
touch agent.py
  • Confirm the file exists by running:
ls

You should see agent.py alongside your requirements.txt and .env files.

  • Open agent.py in your code editor, then paste the following code:
import os
import json
import requests
from moviepy import TextClip, CompositeVideoClip, ColorClip
from dotenv import load_dotenv

# Load environment variables from .env
load_dotenv()

# Configuration constants
GEMINI_API_KEY = os.getenv("GEMINI_API_KEY")
TIKTOK_ACCESS_TOKEN = os.getenv("TIKTOK_ACCESS_TOKEN")
VIDEO_OUTPUT = "quote_video.mp4"
VIDEO_WIDTH = 1080
VIDEO_HEIGHT = 1920
VIDEO_DURATION = 10
VIDEO_FPS = 24


def fetch_daily_quote():
    # Call ZenQuotes API for today's quote
    response = requests.get("https://zenquotes.io/api/today", timeout=10)
    response.raise_for_status()
    data = response.json()
    # Extract quote text and author from the JSON array
    quote = data[0]["q"]
    author = data[0]["a"]
    return quote, author

What does this code do?

  • load_dotenv() reads your .env file and loads the API keys as environment variables.
  • The constants (VIDEO_WIDTH, VIDEO_HEIGHT, etc.) define the TikTok vertical video format: 1080x1920 pixels, 10 seconds long, at 24 FPS.
  • fetch_daily_quote() calls https://zenquotes.io/api/today which returns a JSON array. The quote text lives in the q field and the author lives in the a field.
  • Save agent.py.
  • Test the fetch function by running this command:
python -c "from agent import fetch_daily_quote; q, a = fetch_daily_quote(); print(f'{q} - {a}')"

You should see today's quote printed in your terminal, followed by the author's name.

Seeing a connection error?

  • Make sure you have an active internet connection.
  • Check that your virtual environment is activated. You should see the (venv) prefix in your terminal prompt.
  • If you see ModuleNotFoundError: No module named 'requests', run pip install -r requirements.txt again to install the missing package.

Still stuck? Why can't my script fetch a quote from ZenQuotes?

Create a vertical video with the quote text

MoviePy lets you compose video clips in layers. You will create a solid dark background, overlay the quote text on top of it, and write the result to an MP4 file.

  • In agent.py, add the generate_video function below your fetch_daily_quote function:
def generate_video(quote, author):
    # Create a dark solid background at TikTok dimensions
    background = ColorClip(size=(VIDEO_WIDTH, VIDEO_HEIGHT), color=(20, 20, 20)).with_duration(VIDEO_DURATION)

    # Render the quote as white text, centered on screen
    quote_clip = TextClip(
        font="/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf",
        text=f'"{ quote}"',
        font_size=52,
        color="white",
    ).with_duration(VIDEO_DURATION).with_position("center")

    # Layer the text on top of the background and write to MP4
    final = CompositeVideoClip([background, quote_clip], size=(VIDEO_WIDTH, VIDEO_HEIGHT))
    final.write_videofile(VIDEO_OUTPUT, fps=VIDEO_FPS, codec="libx264")
    return VIDEO_OUTPUT

What does this code do?

  • ColorClip creates a solid-color rectangle at 1080x1920 that lasts 10 seconds. This is the video background.
  • TextClip renders the quote string as an image of white text. The with_position("center") call places it in the middle of the frame.
  • CompositeVideoClip stacks the background and text together. The first clip in the list is the bottom layer.
  • write_videofile encodes the composition into an MP4 using the H.264 codec via FFmpeg.
  • Save agent.py.
Run it and see the result

The last piece is a main() function that ties the fetch and video generation together so you can run the whole pipeline with one command.

  • Add the following code at the bottom of agent.py, below the generate_video function:
def main():
    print("[1/2] Fetching daily quote...")
    quote, author = fetch_daily_quote()
    print(f'  Quote: "{quote}" - {author}')

    print("[2/2] Generating video...")
    video_path = generate_video(quote, author)
    print(f"  Video saved: {video_path}")


if __name__ == "__main__":
    main()

What does this code do?

  • main() runs the pipeline in sequence: fetch quote, then generate video. It prints progress at each stage.
  • The if __name__ == "__main__" guard means main() only runs when you execute the file directly, not when you import it.
  • Save agent.py.

Before you run this, what do you expect to see in the terminal? Think about what each print statement will output.

  • Run the full agent pipeline:
python agent.py

You will see progress messages as the quote is fetched and the video is encoded. FFmpeg will print encoding details while it renders.

  • Once it finishes, confirm the video was created by running:
ls quote_video.mp4

You should see quote_video.mp4 listed. Open the file in your system's video player. You will see white text on a dark background with today's quote, in a vertical 1080x1920 format.

Video not generating?

  • If you see a font-related error, make sure the DejaVu font exists at /usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf. On macOS, try using /System/Library/Fonts/Helvetica.ttc instead.
  • If you see a codec error, confirm FFmpeg is installed by running ffmpeg -version.
  • If the video plays but looks blank, check that your quote text is not empty. Run the fetch test from earlier to confirm the API is returning data.

Need more help? Why is my MoviePy video generation failing?

✔️ Awesome, I've got everything!

Great. Double check you have saved agent.py and that quote_video.mp4 plays correctly in your video player.

ⓧ I'd like to double check the full code

Scroll up and confirm your agent.py contains these sections in order:

  • Imports (os, json, requests, moviepy clips, dotenv) and load_dotenv() call.
  • Configuration constants: GEMINI_API_KEY, TIKTOK_ACCESS_TOKEN, VIDEO_OUTPUT, VIDEO_WIDTH, VIDEO_HEIGHT, VIDEO_DURATION, VIDEO_FPS.
  • fetch_daily_quote() function that calls https://zenquotes.io/api/today and returns (quote, author).
  • generate_video(quote, author) function that creates a ColorClip background, a TextClip for the quote, composes them, and writes to quote_video.mp4.
  • main() function that calls both, plus the if __name__ == "__main__" entry point.

You have a working pipeline that fetches a quote and turns it into a real video file. Next up, you will fix the styling. Right now long quotes may overflow off-screen, and the flat dark background does not exactly scream TikTok. Time to make it look professional.

Fix the Video Styling

Your agent produces a video, but it has problems that would make any TikTok viewer scroll past instantly. Long quotes overflow off the edge of the screen because there is no text wrapping. The flat dark background looks boring. There is no author attribution.

In this step, you will fix all three issues. You will add automatic text wrapping so quotes fit neatly on screen, display the author name below the quote, and replace the flat background with a smooth gradient.

In this step, get ready to:
  • Add text wrapping and author attribution to the quote video.
  • Create a gradient background using Pillow.
Add text wrapping and author attribution

Right now, TextClip renders the quote as a single line. If the quote is longer than 1080 pixels wide, it simply disappears off the edge.

MoviePy's method='caption' parameter tells TextClip to automatically wrap text to fit within a given width. You will also add a second TextClip for the author name.

  • In agent.py, find the generate_video function and replace it entirely with this updated version:
def generate_video(quote, author):
    # Dark solid background (we'll upgrade this in the next substep)
    background = ColorClip(size=(VIDEO_WIDTH, VIDEO_HEIGHT), color=(20, 20, 20)).with_duration(VIDEO_DURATION)

    # Quote text with automatic wrapping at 900px width
    quote_clip = TextClip(
        font="/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf",
        text=f'"{quote}"',
        font_size=52,
        color="white",
        size=(900, None),
        method="caption",
        text_align="center",
    ).with_duration(VIDEO_DURATION).with_position("center")

    # Author name styled smaller and gray, below the quote
    author_clip = TextClip(
        font="/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf",
        text=f"- {author}",
        font_size=36,
        color="#cccccc",
    ).with_duration(VIDEO_DURATION).with_position(("center", 1200))

    # Layer all three clips together
    final = CompositeVideoClip(
        [background, quote_clip, author_clip],
        size=(VIDEO_WIDTH, VIDEO_HEIGHT)
    )
    final.write_videofile(VIDEO_OUTPUT, fps=VIDEO_FPS, codec="libx264")
    return VIDEO_OUTPUT

What does this code do?

  • size=(900, None) constrains the text box to 900 pixels wide. The None height means it grows as tall as needed.
  • method="caption" tells MoviePy to wrap text automatically at word boundaries to fit within that 900px width.
  • text_align="center" centers each wrapped line within the text box.
  • author_clip uses a lighter gray color and smaller font so it looks like a subtitle beneath the quote.
  • with_position(("center", 1200)) places the author at vertical pixel 1200 (below center on a 1920px tall video).
  • Save agent.py.
  • Run the script to see the improved text by running:
python agent.py

Open quote_video.mp4 in your file browser or video player. You should see the quote text wrapped across multiple lines (if the quote is long enough), with the author name displayed in gray below it.

Text still running off-screen?

  • Make sure you included method="caption" inside the TextClip call. Without it, wrapping does not activate.
  • Check that size=(900, None) is present. The width constraint is what triggers the wrapping.
  • If you see a font error, confirm the font file exists at /usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf.

Why isn't my text wrapping in the video?

Create a visually appealing background

The flat dark background works, but it looks amateurish on TikTok. You will replace it with a smooth purple gradient generated by Pillow, then loaded into MoviePy as an image clip.

First, update the imports at the top of agent.py.

  • In agent.py, find this import line at the top:
from moviepy import TextClip, CompositeVideoClip, ColorClip
  • Replace it with these three import lines:
from moviepy import TextClip, CompositeVideoClip, ImageClip
from google import genai
from PIL import Image, ImageDraw

What are these imports for?

  • ImageClip replaces ColorClip. It loads a PNG image as the video background.
  • Image and ImageDraw from Pillow let you draw a custom gradient image pixel by pixel.
  • genai is the Google Gemini SDK. You will use it in the next step to generate captions.
  • Now add a new function called create_gradient_background directly above the generate_video function. Paste this code:
def create_gradient_background(width, height, color_top, color_bottom):
    # Create a blank image and draw a gradient line by line
    img = Image.new("RGB", (width, height))
    draw = ImageDraw.Draw(img)
    for y in range(height):
        # Blend between top and bottom colors based on vertical position
        ratio = y / height
        r = int(color_top[0] * (1 - ratio) + color_bottom[0] * ratio)
        g = int(color_top[1] * (1 - ratio) + color_bottom[1] * ratio)
        b = int(color_top[2] * (1 - ratio) + color_bottom[2] * ratio)
        draw.line([(0, y), (width, y)], fill=(r, g, b))
    img.save("gradient_bg.png")
    return "gradient_bg.png"

What does this function do?

It creates a 1080x1920 PNG image where the color smoothly transitions from dark navy at the top to deep purple at the bottom. Each horizontal line is drawn with a color that blends between color_top and color_bottom based on how far down the image it is.

The result is saved as gradient_bg.png and the file path is returned so MoviePy can load it as a background.

  • Now update generate_video to use the gradient. Find this line inside the function:
    background = ColorClip(size=(VIDEO_WIDTH, VIDEO_HEIGHT), color=(20, 20, 20)).with_duration(VIDEO_DURATION)
  • Replace it with these lines that call your new gradient function and load the result as an image clip:
    bg_path = create_gradient_background(
        VIDEO_WIDTH, VIDEO_HEIGHT,
        color_top=(20, 20, 60),
        color_bottom=(80, 20, 100)
    )

    background = ImageClip(bg_path).with_duration(VIDEO_DURATION)
  • Save agent.py.

Before you run the script, what do you expect the video to look like now compared to the previous run?

  • Run the agent to generate the updated video:
python agent.py

Open quote_video.mp4. You should see the quote wrapped neatly across multiple lines with the author name below, all on a smooth navy-to-purple gradient background fitting within the 1080x1920 frame.

Seeing a flat color instead of a gradient?

  • Make sure you removed the old ColorClip line and replaced it with the create_gradient_background call followed by ImageClip(bg_path).
  • Check that gradient_bg.png was created in your project folder. If it is missing, the create_gradient_background function is not being called.
  • Confirm you updated the import from ColorClip to ImageClip at the top of the file.

My gradient background isn't showing in the video. Help me debug.

✔️ Awesome, I've got everything!

Great. Double check you have saved agent.py and your video plays with the gradient background and wrapped text.

ⓧ I'd like to double check the full code

Here is the complete agent.py at this point. Compare yours against it:

import os
import json
import requests
from moviepy import TextClip, CompositeVideoClip, ImageClip
from google import genai
from PIL import Image, ImageDraw
from dotenv import load_dotenv

load_dotenv()

GEMINI_API_KEY = os.getenv("GEMINI_API_KEY")
TIKTOK_ACCESS_TOKEN = os.getenv("TIKTOK_ACCESS_TOKEN")
VIDEO_OUTPUT = "quote_video.mp4"
VIDEO_WIDTH = 1080
VIDEO_HEIGHT = 1920
VIDEO_DURATION = 10
VIDEO_FPS = 24
def fetch_daily_quote():
    response = requests.get("https://zenquotes.io/api/today", timeout=10)
    response.raise_for_status()
    data = response.json()
    quote = data[0]["q"]
    author = data[0]["a"]
    return quote, author
def create_gradient_background(width, height, color_top, color_bottom):
    img = Image.new("RGB", (width, height))
    draw = ImageDraw.Draw(img)
    for y in range(height):
        ratio = y / height
        r = int(color_top[0] * (1 - ratio) + color_bottom[0] * ratio)
        g = int(color_top[1] * (1 - ratio) + color_bottom[1] * ratio)
        b = int(color_top[2] * (1 - ratio) + color_bottom[2] * ratio)
        draw.line([(0, y), (width, y)], fill=(r, g, b))
    img.save("gradient_bg.png")
    return "gradient_bg.png"
def generate_video(quote, author):
    bg_path = create_gradient_background(
        VIDEO_WIDTH, VIDEO_HEIGHT,
        color_top=(20, 20, 60),
        color_bottom=(80, 20, 100)
    )

    background = ImageClip(bg_path).with_duration(VIDEO_DURATION)

    quote_clip = TextClip(
        font="/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf",
        text=f'"{quote}"',
        font_size=52,
        color="white",
        size=(900, None),
        method="caption",
        text_align="center",
    ).with_duration(VIDEO_DURATION).with_position("center")

    author_clip = TextClip(
        font="/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf",
        text=f"- {author}",
        font_size=36,
        color="#cccccc",
    ).with_duration(VIDEO_DURATION).with_position(("center", 1200))

    final = CompositeVideoClip(
        [background, quote_clip, author_clip],
        size=(VIDEO_WIDTH, VIDEO_HEIGHT)
    )
    final.write_videofile(VIDEO_OUTPUT, fps=VIDEO_FPS, codec="libx264")
    return VIDEO_OUTPUT
def main():
    print("[1/2] Fetching daily quote...")
    quote, author = fetch_daily_quote()
    print(f'  Quote: "{quote}" - {author}')

    print("[2/2] Generating video...")
    video_path = generate_video(quote, author)
    print(f"  Video saved: {video_path}")


if __name__ == "__main__":
    main()

Your videos now look polished enough for TikTok. Next up, you will connect to Gemini AI so the agent can automatically write engaging captions with hashtags for each quote.

Generate a TikTok Caption with Gemini

Your video pipeline now produces polished clips with gradient backgrounds and properly wrapped text. The next challenge is engagement. On TikTok, the caption and hashtags determine whether the algorithm surfaces your content to viewers.

Instead of writing a caption manually every day, you'll connect the agent to Gemini. The AI will read the quote and craft a punchy, Gen-Z-friendly caption with trending hashtags automatically.

In this step, get ready to:
  • Connect to Gemini and generate a TikTok caption from the daily quote.
  • Integrate caption generation into the main agent pipeline.
Connect to Gemini and generate a caption

The google-genai SDK is already imported in your file from the earlier setup. Now you'll write a function that sends the daily quote to Gemini and gets back a ready-to-post caption.

  • In agent.py, find the end of the generate_video function (after the return VIDEO_OUTPUT line).
  • Add the following generate_caption function below it:
def generate_caption(quote, author):
    # Initialize the Gemini client with your API key
    client = genai.Client(api_key=GEMINI_API_KEY)
    # Build a prompt requesting a short TikTok caption with hashtags
    prompt = (
        f"Generate a short, engaging TikTok caption for this quote: "
        f'"{ quote}" by {author}. '
        f"Include 3-5 relevant hashtags. Keep it under 150 characters total. "
        f"Be conversational and Gen-Z friendly. Return only the caption text."
    )
    # Send the prompt to Gemini 2.5 Flash and return the response text
    response = client.models.generate_content(
        model="gemini-2.5-flash",
        contents=prompt,
    )
    return response.text.strip()

What does this code do?

  • genai.Client(api_key=GEMINI_API_KEY) creates a Gemini client authenticated with the key from your .env file.
  • The prompt is carefully crafted to constrain the output: under 150 characters, conversational tone, and 3-5 hashtags. Telling Gemini to "return only the caption text" prevents it from adding explanations or formatting.
  • client.models.generate_content() sends the prompt to the gemini-2.5-flash model and returns the AI-generated text.
  • .strip() removes any leading or trailing whitespace from the response.
  • Save agent.py.

You won't be able to test this function on its own yet because nothing calls it. The next substep wires it into your pipeline so you can see it in action.

Integrate caption generation into the pipeline

Your main() function currently runs two steps: fetch the quote and generate the video. You need to add a third step that calls generate_caption and prints the result.

  • In agent.py, find the main() function at the bottom of the file.
  • Find this code:
def main():
    print("[1/2] Fetching daily quote...")
    quote, author = fetch_daily_quote()
    print(f'  Quote: "{quote}" - {author}')

    print("[2/2] Generating video...")
    video_path = generate_video(quote, author)
    print(f"  Video saved: {video_path}")
  • Replace the entire main() function with this updated version:
def main():
    print("[1/3] Fetching daily quote...")
    quote, author = fetch_daily_quote()
    print(f'  Quote: "{quote}" - {author}')

    print("[2/3] Generating video...")
    video_path = generate_video(quote, author)
    print(f"  Video saved: {video_path}")

    print("[3/3] Generating TikTok caption with AI...")
    caption = generate_caption(quote, author)
    print(f"  Caption: {caption}")

What changed?

  • The step numbers changed from [1/2] and [2/2] to [1/3] and [2/3] to reflect the new third step.
  • The new [3/3] block calls generate_caption(quote, author) and prints the AI-generated caption to the terminal.
  • The caption is generated after the video so the full pipeline runs in order: fetch, render, caption.
  • Save agent.py.

Before you run this, what do you expect to see in the terminal alongside the video generation output?

  • Run the agent by executing this command:
python agent.py

You should see all three steps printed in the terminal. After the video generates, a fresh AI-written caption appears with hashtags like:

[1/3] Fetching daily quote...
  Quote: "The only way to do great work is to love what you do." - Steve Jobs
[2/3] Generating video...
  Video saved: quote_video.mp4
[3/3] Generating TikTok caption with AI...
  Caption: Start your morning right. This quote hits different. #motivation #dailyquotes #mindset

Your caption will be different each time because Gemini generates a unique response for every request.

Seeing an authentication error?

  • Check that your .env file contains GEMINI_API_KEY= followed by your actual key from Google AI Studio. The placeholder value your-gemini-api-key-here will not work.
  • Make sure your API key was generated in Google AI Studio at aistudio.google.com. Keys generated there are restricted to the Gemini API by default.
  • If you see a timeout or network error, check your internet connection and try running the script again. The SDK automatically retries transient errors.

Help me debug my Gemini API authentication error in the generate_caption function.

✔️ Awesome, I've got everything!

Great. Double check you've saved agent.py and move on to the next step.

ⓧ I'd like to double check the full code

Your agent.py should now contain these sections in order:

  • Imports and load_dotenv() at the top.
  • Constants (GEMINI_API_KEY, TIKTOK_ACCESS_TOKEN, VIDEO_OUTPUT, dimensions, duration, FPS).
  • fetch_daily_quote() function.
  • create_gradient_background() function.
  • generate_video() function with gradient background, quote TextClip, and author TextClip.
  • generate_caption() function (the new one you added in this step).
  • main() function with three steps: fetch, video, caption.
  • if __name__ == "__main__": main() at the very bottom.

Your agent now fetches a quote, renders a video, and crafts an AI caption in one run. Next up, you'll wire in the TikTok Content Posting API so the agent can publish the video directly to your account.

Post to TikTok

Your agent can fetch quotes, render beautiful videos, and write AI-powered captions. But all of that stays on your local machine.

The final piece of the automation puzzle is pushing the video directly to TikTok so the pipeline is truly end-to-end. In this step, you will use the TikTok Content Posting API to upload your generated video and caption in a single function call.

In this step, get ready to:
  • Build a function that initializes a video upload with TikTok's API and transfers the file.
  • Handle the case where no access token is configured yet.
  • Wire posting into the main pipeline so one command does everything.
Set up the TikTok posting function

TikTok uses OAuth2 to authenticate API requests. Your access token (stored in .env) proves your app has permission to post on behalf of a TikTok account. The function you are about to write checks for that token first and skips gracefully if it is missing.

Why does the function skip instead of crashing?

TikTok app review can take days. By skipping the upload when no token is present, you can develop and test the rest of the pipeline immediately. Once your app is approved and you have a real access token, the same code works without changes.

  • In agent.py, add the post_to_tiktok function below the generate_caption function. This first part handles the token check and builds the init request:
def post_to_tiktok(video_path, caption):
    """Post video to TikTok via Content Posting API."""
    # Skip posting if no access token is configured
    if not TIKTOK_ACCESS_TOKEN:
        print("[SKIP] No TikTok access token found. Set TIKTOK_ACCESS_TOKEN in .env")
        print(f"[READY] Video: {video_path}")
        print(f"[READY] Caption: {caption}")
        return None

    video_size = os.path.getsize(video_path)

    # Initialize the upload via TikTok's Content Posting API
    init_url = "https://open.tiktokapis.com/v2/post/publish/video/init/"
    headers = {
        "Authorization": f"Bearer {TIKTOK_ACCESS_TOKEN}",
        "Content-Type": "application/json; charset=UTF-8",
    }
    payload = {
        "post_info": {
            "title": caption,
            "privacy_level": "PUBLIC_TO_EVERYONE",
            "disable_duet": False,
            "disable_comment": False,
            "disable_stitch": False,
        },
        "source_info": {
            "source": "FILE_UPLOAD",
            "video_size": video_size,
            "chunk_size": video_size,
            "total_chunk_count": 1,
        },
    }

What does this code do?

  • The token check at the top lets you run the full pipeline even before TikTok approves your app. It prints the video path and caption so you can verify everything else works.
  • os.path.getsize(video_path) gets the exact file size in bytes. TikTok needs this to allocate upload space.
  • The post_info object sets the caption (title), visibility level, and interaction permissions for the published video.
  • The source_info object tells TikTok you are uploading one chunk equal to the full file size. For videos under 64MB, a single chunk works perfectly.

What about unaudited apps?

Until your TikTok app passes audit, all content posted via the API is restricted to SELF_ONLY visibility. The video posts successfully, but only you can see it. Once TikTok approves your app, the same code posts publicly with no changes needed.

Upload the video file to TikTok

The init request tells TikTok to expect a file. TikTok responds with an upload_url where you PUT the actual video bytes. This two-step pattern (init then upload) is how TikTok handles large file transfers reliably.

  • Directly below the payload dictionary you just added (still inside the post_to_tiktok function), add the code that sends the init request and uploads the video:
    # Send the init request to get an upload URL
    response = requests.post(init_url, headers=headers, json=payload, timeout=30)
    response.raise_for_status()
    result = response.json()

    if result.get("error", {}).get("code") != "ok":
        print(f"[ERROR] TikTok init failed: {result}")
        return None

    upload_url = result["data"]["upload_url"]
    publish_id = result["data"]["publish_id"]

    # Read the video file and upload it to TikTok
    with open(video_path, "rb") as video_file:
        video_data = video_file.read()

    upload_headers = {
        "Content-Type": "video/mp4",
        "Content-Length": str(video_size),
        "Content-Range": f"bytes 0-{video_size - 1}/{video_size}",
    }
    upload_response = requests.put(
        upload_url, headers=upload_headers, data=video_data, timeout=120
    )
    upload_response.raise_for_status()

    print(f"[SUCCESS] Video posted to TikTok! publish_id: {publish_id}")
    return publish_id

What does this code do?

  • The POST to the init endpoint returns a JSON response. If the error.code field is not "ok", something went wrong and the function exits early.
  • upload_url is a one-time URL valid for one hour. You PUT the raw video bytes there.
  • The Content-Range header tells TikTok which byte range you are sending. Since this is a single-chunk upload, it is the entire file: bytes 0-(size-1)/size.
  • publish_id is your receipt. You can use it later to check the post's status.
  • Save agent.py.

Seeing a syntax error on save?

  • Make sure the second code chunk is indented at the same level as the payload variable above it. Everything after def post_to_tiktok must be one level in.
  • Check that you did not accidentally leave a blank line between the payload closing brace and the response = requests.post(...) line that breaks indentation context.

Still stuck? Why is my post_to_tiktok function showing a syntax error?

Assemble the full pipeline

Right now main() runs three steps: fetch, generate video, generate caption. You need to add the fourth step that posts to TikTok, completing the end-to-end pipeline.

  • In agent.py, find the existing main() function and replace it entirely with this updated version:
def main():
    """Run the full agent pipeline."""
    print("[1/4] Fetching daily quote...")
    quote, author = fetch_daily_quote()
    print(f'  Quote: "{quote}" - {author}')

    print("[2/4] Generating video...")
    video_path = generate_video(quote, author)
    print(f"  Video saved: {video_path}")

    print("[3/4] Generating TikTok caption with AI...")
    caption = generate_caption(quote, author)
    print(f"  Caption: {caption}")

    print("[4/4] Posting to TikTok...")
    post_to_tiktok(video_path, caption)

    print("\nDone! Agent pipeline complete.")

What changed in main()?

  • The step counters changed from [1/3], [2/3], [3/3] to [1/4], [2/4], [3/4], [4/4] to reflect the complete pipeline.
  • Step 4 calls post_to_tiktok(video_path, caption) which either uploads the video or skips with a helpful message.
  • A final "Done!" line confirms the entire pipeline executed.
  • Save agent.py.

Before you run the script, what do you think will happen at step 4? Will it post successfully or skip?

  • Run the full pipeline by executing:
python agent.py

Since your .env still has the placeholder value your-tiktok-access-token-here, the function treats it as a real token and attempts the API call. You will see the quote fetched, video generated, caption created, and then a connection error or HTTP error at step 4. That is expected.

Test the skip path instead

To see the graceful skip message, temporarily clear the token value in .env so the line reads TIKTOK_ACCESS_TOKEN= (empty after the equals sign). Run the script again and you will see:

[SKIP] No TikTok access token found. followed by [READY] Video: and [READY] Caption: lines confirming the pipeline ran everything else successfully.

Script crashes before reaching step 4?

  • If you see a NameError for post_to_tiktok, make sure the function definition appears above main() in the file.
  • If the video generation step fails, confirm your virtual environment is still active and ffmpeg is installed.

Need help? My agent.py crashes when I try to run the full pipeline

✔️ Awesome, I've got everything!

Great. Double check you have saved agent.py and that the pipeline runs all four steps in sequence.

ⓧ I'd like to double check the full code

Your agent.py is now over 100 lines. Scroll through and confirm these sections appear in order:

  • Imports and load_dotenv() at the top.
  • Constants: GEMINI_API_KEY, TIKTOK_ACCESS_TOKEN, VIDEO_OUTPUT, VIDEO_WIDTH, VIDEO_HEIGHT, VIDEO_DURATION, VIDEO_FPS.
  • fetch_daily_quote() function.
  • create_gradient_background() function.
  • generate_video() function.
  • generate_caption() function.
  • post_to_tiktok() function with token check, init POST, and file PUT.
  • main() with four numbered steps ending in post_to_tiktok(video_path, caption).
  • if __name__ == "__main__": block at the very bottom.

Your agent pipeline is complete. One command now fetches a quote, renders a polished video, writes an AI caption, and pushes it all to TikTok. Once your app passes review and you set a real access token, this script posts content for you automatically.

Secret mission

Automate with Cron

Your agent works, but you have to run it manually every time. A true AI agent runs itself on a schedule. In this secret mission, you will make the script production-ready with logging, retry logic, and absolute paths, then schedule it with cron to post a fresh quote video every single day without you lifting a finger.

Clean Up Your Resources

Clean Up Your Resources

Decide whether to keep your resources running, pause them to come back later, or delete them entirely. This project uses free-tier services and local files, so there are no ongoing costs.

Resources you used:

  • TikTok Developer App on developers.tiktok.com.
  • Gemini API key on aistudio.google.com (free tier, no charges unless you add billing).
  • Cron job scheduling the agent daily (if you completed the Secret Mission).
  • Local project files on your Desktop (agent.py, .env, requirements.txt, generated videos, virtual environment).

Keep everything running

No action needed. Choose this if you are still actively building or want the agent to keep posting daily.

  • Your TikTok Developer App remains active and free.
  • Your Gemini API key stays on the free tier with no charges.
  • Your cron job (if set up) continues running the agent on schedule.
  • Your local project files stay on your Desktop.

Pause - I'll come back to this later

Stop the agent from running automatically while keeping everything intact for later.

  • Disable the cron job by running:
crontab -e
  • Find the line with your agent script and add a # at the beginning to comment it out.
  • Save and exit the editor.

Everything else (your TikTok app, Gemini key, and project files) stays in place for when you return.

Delete - I don't want to use this again

Remove all project resources and start fresh. Follow each step below.

  • Remove the cron job (if you set one up) by running:
crontab -e

Delete the entire line containing your agent script path, then save and exit.

  • Delete your TikTok Developer App:
  • Go to developers.tiktok.com and sign in.
  • Click Manage apps.
  • On your app's page, click the More Options [...] button.
  • Select Delete the app and confirm.
  • Delete your Gemini API key:
  • Open the Google Cloud API Credentials page at console.cloud.google.com/apis/credentials.
  • Find the API key you created for this project and click the Actions icon.
  • Select Delete API key.
  • In the confirmation modal, click Delete.
  • Delete your local project files by running:
cd ~/Desktop
rm -rf [[PROJECT_FOLDER="your project folder name"]]

Check your folder name

If you are unsure what your project folder is called, run ls ~/Desktop to find it.

That's a Wrap!

That's a Wrap!

Nice work! You just built a fully automated AI agent that creates and posts TikTok content without you lifting a finger.

You've learned how to:

  • Chain multiple APIs (ZenQuotes, Gemini, TikTok Content Posting API) into a single automation pipeline that fetches, generates, and posts content in one script execution.
  • Generate TikTok-ready vertical videos (1080x1920) with styled text overlays, gradient backgrounds, and automatic text wrapping using MoviePy and Pillow.
  • Implement the TikTok FILE_UPLOAD flow with OAuth2 authentication to upload videos programmatically via the Content Posting API.
  • Secret Mission: Scheduled the agent to run daily with cron for fully hands-off automation, complete with logging and retry logic.

Ready to quiz yourself?