---
title: "How to add callout arrows and circles with Claude (tested prompt and script)"
description: "A red marker loop scribbles itself around the bottle's dropper cap, then a curved arrow swoops in from the top-right corner and points at it. Install one skill and ask Claude Code, or run the Python script we tested yourself."
url: https://www.recharm.com/effects/callout-arrows-and-circles
---

# How to add callout arrows and circles with Claude

A red marker loop scribbles itself around the bottle's dropper cap, then a curved arrow swoops in from the top-right corner and points at it.

Made by the script on this page, from our own test clip.

## Get this effect

1. ### Switch to Code in your [Claude Desktop App](https://claude.com/download)

2. ### Install the skill (once)

   ```
   Install the Recharm video effects skill: download https://recharm-growth-staging.s3.us-east-2.amazonaws.com/website/downloads/recharm-video-effects.zip and unzip it into ~/.claude/skills
   ```

3. ### Ask for the effect

   ```
   Add callout arrows and circles to input.mp4
   Second the circle starts drawing: 0.6
   Seconds the circle takes to draw: 0.7
   ```

## Several videos, several effects, one ask

Put your clips in the folder, mention them with @, and ask for everything at once. Claude runs one tested script after another on each clip, then joins them.

Add callout arrows and circles to **@clip1.mp4**, put a sale badge on **@clip2.mp4**, give **@clip3.mp4** a VHS look, then join all three with crossfades.

## Settings you can change

Ask Claude to change any of these, or edit them at the top of the script.

| Setting                | Default                      | What it does                                                     |
| ---------------------- | ---------------------------- | ---------------------------------------------------------------- |
| `CIRCLE_AT`            | `0.6`                        | Second the circle starts drawing.                                |
| `CIRCLE_DRAW`          | `0.7`                        | Seconds the circle takes to draw.                                |
| `ARROW_AT`             | `1.4`                        | Second the arrow starts drawing.                                 |
| `ARROW_DRAW`           | `0.5`                        | Seconds the arrow takes, head included.                          |
| `CENTER, RADII`        | `(0.5, 0.30), (0.20, 0.105)` | Where the circle sits and how big it is, as shares of the frame. |
| `ARROW_FROM, ARROW_TO` | `(0.90, 0.08), (0.74, 0.25)` | The arrow's tail and tip.                                        |
| `COLOR`                | `(230, 30, 45)`              | Marker color.                                                    |
| `THICKNESS`            | `0.018`                      | Stroke width as a share of the frame width.                      |

## Without the skill

Don’t want to install the skill? Paste this prompt into Claude Code and Claude writes the script itself.

```
Add a hand-drawn callout to input.mp4 that points at the bottle's dropper cap. Draw everything in red (230, 30, 45) marker strokes 1.8% of the frame width thick with round ends, as if written on. First, from 0.6 to 1.3 seconds, a loose circle draws itself around the dropper: an ellipse centered 50% across and 30% down, 20% of the frame width wide as its horizontal radius and 10.5% of the frame height as its vertical radius, tilted 8 degrees counterclockwise, starting at the upper left and going round 1.12 turns while spiralling in from 104% to 94% of its radius (with a slight wobble) so the ends overlap like a pen scribble. Then, from 1.4 to 1.75 seconds, a curved arrow shaft draws from 90% across and 8% down, bowing down and out, to its tip at 74% across and 25% down, just outside the circle's right side; from 1.75 to 1.9 seconds the two strokes of its arrowhead draw in, each 7.5% of the frame width long and 32 degrees either side of the shaft's direction. Ease each stroke in and out, keep everything on screen to the end, and leave the rest of the frame untouched. Use Python with Pillow to draw it (no text, and don't rely on ffmpeg's drawtext filter) and ffmpeg to put it on the video, keep the audio, and save it as output.mp4.
```

## Run it without Claude

This is the exact script we tested. Save it as `callout-arrows-and-circles.py` and run:

```
python3 callout-arrows-and-circles.py input.mp4 output.mp4
```

```
#!/usr/bin/env python3
"""Callout arrows and circles: a red marker circle scribbles round one detail, then a curved arrow draws in pointing at it.

Usage: python3 callout-arrows-and-circles.py input.mp4 output.mp4
Needs ffmpeg and Pillow (pip install pillow).
"""
import json
import math
import subprocess
import sys

from PIL import Image, ImageDraw

CIRCLE_AT = 0.6              # second the circle starts drawing
CIRCLE_DRAW = 0.7            # seconds the circle takes to draw
ARROW_AT = 1.4               # second the arrow starts drawing
ARROW_DRAW = 0.5             # seconds the arrow takes, head included
CENTER = (0.5, 0.30)         # circle center (0-1 across and down the frame)
RADII = (0.20, 0.105)        # circle radii: across as a share of the width, down as a share of the height
ARROW_FROM = (0.90, 0.08)    # where the arrow's tail starts
ARROW_TO = (0.74, 0.25)      # where its tip lands
COLOR = (230, 30, 45)        # marker color
THICKNESS = 0.018            # stroke width as a share of the frame width
SS = 2                       # draw at twice the size and shrink, for smooth edges

def ease_in_out(p):
    p = min(max(p, 0), 1)
    return p * p * (3 - 2 * p)

def circle_path():
    """A loose hand-drawn loop: 1.12 turns, slightly tilted, wobbling and spiralling in so the ends overlap."""
    cx, cy = CENTER[0] * w, CENTER[1] * h
    rx, ry = RADII[0] * w, RADII[1] * h
    tilt = math.radians(-8)
    points = []
    for i in range(201):
        p = i / 200
        a = math.radians(-120) + p * 2 * math.pi * 1.12
        wobble = (1 + 0.035 * math.sin(3 * a + 1) + 0.02 * math.sin(5 * a)) * (1.04 - 0.1 * p)
        x, y = rx * wobble * math.cos(a), ry * wobble * math.sin(a)
        points.append((cx + x * math.cos(tilt) - y * math.sin(tilt), cy + x * math.sin(tilt) + y * math.cos(tilt)))
    return points

def arrow_paths():
    """A shaft bowing outward from the tail to the tip, and the two strokes of its head."""
    (x0, y0), (x1, y1) = (ARROW_FROM[0] * w, ARROW_FROM[1] * h), (ARROW_TO[0] * w, ARROW_TO[1] * h)
    bx, by = x0 + (x1 - x0) * 0.1, y0 + (y1 - y0) * 0.75  # control point: the shaft sags down, then comes in
    shaft = [((1 - p) ** 2 * x0 + 2 * (1 - p) * p * bx + p * p * x1,
              (1 - p) ** 2 * y0 + 2 * (1 - p) * p * by + p * p * y1) for p in (i / 60 for i in range(61))]
    heading = math.atan2(y1 - by, x1 - bx)
    size = w * 0.075
    head = [[(x1 - size * math.cos(heading + s * math.radians(32)), y1 - size * math.sin(heading + s * math.radians(32))),
             (x1, y1)] for s in (-1, 1)]
    return shaft, head

def stroke(draw, points, p):
    """Draw the first share p of a path with round ends, like a marker pen."""
    if p <= 0:
        return
    xy = [(x * SS, y * SS) for x, y in points[:max(2, round(len(points) * p))]]
    width = round(w * THICKNESS * SS)
    draw.line(xy, fill=COLOR, width=width, joint="curve")
    for x, y in (xy[0], xy[-1]):
        draw.ellipse([x - width / 2, y - width / 2, x + width / 2, y + width / 2], fill=COLOR)

def overlay(t):
    if t < CIRCLE_AT:
        return Image.new("RGBA", (w, h))
    img = Image.new("RGBA", (w * SS, h * SS))
    draw = ImageDraw.Draw(img)
    stroke(draw, circle, ease_in_out((t - CIRCLE_AT) / CIRCLE_DRAW))
    p = (t - ARROW_AT) / ARROW_DRAW  # the shaft takes the first 70%, the head the rest
    stroke(draw, shaft, ease_in_out(p / 0.7))
    for line in head:
        stroke(draw, line, ease_in_out((p - 0.7) / 0.3))
    return img.resize((w, h), Image.LANCZOS)

def probe(path):
    info = json.loads(subprocess.check_output(["ffprobe", "-v", "error", "-select_streams", "v:0", "-show_entries",
                                               "stream=width,height,r_frame_rate:format=duration", "-of", "json", path]))
    s = info["streams"][0]
    num, den = s["r_frame_rate"].split("/")
    return s["width"], s["height"], s["r_frame_rate"], float(num) / float(den), float(info["format"]["duration"])

def render(src, out):
    """Draw overlay(t) on every frame of src and save to out, keeping the audio."""
    ff = subprocess.Popen(["ffmpeg", "-y", "-v", "error", "-i", src,
                           "-f", "rawvideo", "-pix_fmt", "rgba", "-s", f"{w}x{h}", "-r", rate, "-i", "-",
                           "-filter_complex", "[0:v][1:v]overlay=shortest=1,format=yuv420p[v]",
                           "-map", "[v]", "-map", "0:a?", "-c:v", "libx264", "-crf", "18", "-c:a", "copy", out],
                          stdin=subprocess.PIPE)
    for i in range(math.ceil(duration * fps)):
        ff.stdin.write(overlay(i / fps).tobytes())
    ff.stdin.close()
    if ff.wait():
        sys.exit("ffmpeg failed")

src, out = sys.argv[1:3]
w, h, rate, fps, duration = probe(src)
circle = circle_path()
shaft, head = arrow_paths()
render(src, out)
```

## More product shots

- [Animated feature callouts **Animated feature callouts**](https://www.recharm.com/effects/animated-feature-callouts)
- [Before/after slider **Before/after slider**](https://www.recharm.com/effects/before-after-slider)
- [Best seller badge **Best seller badge**](https://www.recharm.com/effects/best-seller-badge)
- [Carousel swipe **Carousel swipe**](https://www.recharm.com/effects/carousel-swipe)
- [Comparison table **Comparison table**](https://www.recharm.com/effects/comparison-table)

## Find 3x _winning ads_

Recharm cuts your ad footage into tagged, searchable clips: hooks, testimonials, product shots. Find the shots that win, add the effect with Claude, and ship 3x more ad edits.

[Start 14-day free trial](https://www.recharm.com/get-started)
