Video Caption

Extract video audio, request timestamped subtitles, and embed captions with MoviePyVideoTools.

video_caption.py
"""
Video Caption
=============================

Please install dependencies using:.
"""

from agno.agent import Agent
from agno.models.openai import OpenAIResponses
from agno.tools.moviepy_video import MoviePyVideoTools
from agno.tools.openai import OpenAITools

video_tools = MoviePyVideoTools(
    enable_process_video=True, enable_generate_captions=True, enable_embed_captions=True
)

openai_tools = OpenAITools()

# ---------------------------------------------------------------------------
# Create Agent
# ---------------------------------------------------------------------------
video_caption_agent = Agent(
    name="Video Caption Generator Agent",
    model=OpenAIResponses(
        id="gpt-5.6-luna",
    ),
    tools=[video_tools, openai_tools],
    description="You are an AI agent that can generate and embed captions for videos.",
    instructions=[
        "When a user provides a video, process it to generate captions.",
        "Use the video processing tools in this sequence:",
        "1. Extract audio from the video using extract_audio",
        "2. Transcribe the audio using transcribe_audio",
        "3. Generate SRT captions using create_srt",
        "4. Embed captions into the video using embed_captions",
    ],
    markdown=True,
)

# ---------------------------------------------------------------------------
# Run Agent
# ---------------------------------------------------------------------------
if __name__ == "__main__":
    video_caption_agent.print_response(
        "Generate captions for {video with location} and embed them in the video"
    )

Request timestamped subtitles

OpenAITools.transcribe_audio in the archived program requests plain text, while create_srt only writes the supplied text and embed_captions expects timed SRT blocks. Use this replacement program as caption_video.py to request provider timestamps and check them before embedding. It calls the toolkit directly so an intermediate model cannot invent or rewrite the timing.

caption_video.py
import argparse
import math
from pathlib import Path
from tempfile import TemporaryDirectory

from agno.tools.moviepy_video import MoviePyVideoTools
from moviepy import VideoFileClip
from openai import OpenAI


def caption_video(video_path: Path, output_path: Path) -> None:
    if not video_path.is_file():
        raise FileNotFoundError(video_path)
    tools = MoviePyVideoTools(enable_embed_captions=True)
    output_path.parent.mkdir(parents=True, exist_ok=True)
    with TemporaryDirectory() as directory:
        audio_path = Path(directory) / "audio.mp3"
        with VideoFileClip(str(video_path)) as video:
            if video.audio is None:
                raise ValueError("Video has no audio track")
            video.audio.write_audiofile(str(audio_path))
        with OpenAI() as client, audio_path.open("rb") as audio_file:
            subtitles = client.audio.transcriptions.create(
                model="whisper-1",
                file=audio_file,
                response_format="srt",
            )
        if not isinstance(subtitles, str):
            raise RuntimeError("Transcription did not return SRT text")
        words = tools.parse_srt(subtitles)
        if not words or any(
            not math.isfinite(word["start"])
            or not math.isfinite(word["end"])
            or word["start"] < 0
            or word["end"] <= word["start"]
            for word in words
        ):
            raise ValueError("Transcription has no usable subtitle timing")
        srt_path = Path(directory) / "captions.srt"
        srt_path.write_text(subtitles, encoding="utf-8")
        result = tools.embed_captions(
            video_path=str(video_path),
            srt_path=str(srt_path),
            output_path=str(output_path),
        )
        if result != str(output_path) or not output_path.is_file():
            raise RuntimeError(f"Caption export failed: {result}")
    print(f"Saved {output_path}")


if __name__ == "__main__":
    parser = argparse.ArgumentParser()
    parser.add_argument("video", type=Path)
    parser.add_argument("output", type=Path)
    args = parser.parse_args()
    caption_video(args.video, args.output)

Whisper supplies segment timings. MoviePyVideoTools distributes words evenly within each segment for highlighting; those word times are approximate. Check the transcription against the recording before sharing it. MoviePy also requires a working FFmpeg installation and a font usable by its caption renderer. The toolkit's default caption font is Arial.

The OpenAI transcription API supports SRT for whisper-1. Its announced shutdown is February 26, 2027; verify timestamp-format support when migrating models.

Run the Example

Set up your virtual environment

uv venv --python 3.12
source .venv/bin/activate

Install dependencies

uv pip install -U agno moviepy openai

Export your OpenAI API key

export OPENAI_API_KEY="your_openai_api_key_here"

Set the video path

Choose a local video with an audio track. The command below uses input.mp4 and writes a separate output-captioned.mp4; replace both paths as needed.

Run the example

Save the replacement program above as caption_video.py, then run:

python caption_video.py input.mp4 output-captioned.mp4

Full source: cookbook/02_agents/12_multimodal/video_caption.py