How to add a hexagon transition with Claude

Honeycomb cells of clip B grow open from the middle of the frame outward, each white-rimmed hexagon widening until the cells lock together and clip A is gone, over 1.2 seconds.

Get this effect

  1. Switch to Code in your Claude Desktop App

  2. Install the skill (once)

    Show the prompt
    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

    Join clipA.mp4 and clipB.mp4 with a hexagon transition
    Length of the transition in seconds: 1.2
    Hexagon size from center to corner, in pixels: 60

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.

Put a sale badge on @clip1.mp4, give @clip2.mp4 a VHS look, then join them with a hexagon transition.

Settings you can change

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

SettingDefaultWhat it does
D1.2Length of the transition in seconds.
R60Hexagon size from center to corner, in pixels.
GROW0.5Share of the transition each cell takes to open; the rest staggers the cells outward from the center.
RIM0.08Thickness of the white rim, as a share of a cell's center-to-edge distance (about 4 pixels).
RIM_COLOR(255, 255, 255)Color of the rim on each growing hexagon.

Without the skill

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

Show the prompt
Join clipA.mp4 and clipB.mp4 (both 540x960, 30fps) with a 1.2 second hexagon transition starting 1.2 seconds before A ends. Lay a honeycomb of pointy-top hexagons over the frame, 60 pixels from center to corner (104 pixels across the flat sides), with one cell centered exactly on the frame's center (270, 480). Each cell opens onto clip B as a hexagon growing from the cell's center, eased in and out (smoothstep) over 0.6 seconds, growing to 1.05 times the cell's size so neighbors close with no seams. The middle cell starts at once and the others start later in proportion to their centers' distance from the frame's center, the furthest cell starting 0.6 seconds in, so the honeycomb spreads outward and the whole transition ends at 1.2 seconds. While a cell is growing, draw a 4 pixel pure white (255, 255, 255) rim just inside its hexagon's edge; no rims before or after the transition. Crossfade the audio over the same 1.2 seconds. Match B's size and frame rate to A. Use Python with numpy (and Pillow for any drawing, not ffmpeg's drawtext) and ffmpeg, keep the audio and save it as output.mp4 (H.264, yuv420p, 30fps, 10.8 seconds long).

Run it without Claude

This is the exact script we tested. Save it as hexagon-transition.py and run:

python3 hexagon-transition.py clipA.mp4 clipB.mp4 output.mp4
Show the script
#!/usr/bin/env python3
"""Hexagon transition: honeycomb cells grow open from the center outward, each with a white rim, swapping clip A for clip B over 1.2s.

Usage: python3 hexagon-transition.py clipA.mp4 clipB.mp4 output.mp4  (both clips need an audio track)
Needs ffmpeg, numpy and Pillow (pip install numpy pillow).
"""
import json
import subprocess
import sys
from itertools import islice

import numpy as np

D = 1.2                  # transition length in seconds
FPS = 30                 # output frame rate
R = 60                   # hexagon size: center to corner, in pixels
GROW = 0.5               # share of the transition each cell takes to open
RIM = 0.08               # rim thickness, as a share of the cell's center-to-edge distance (about 4 px)
RIM_COLOR = (255, 255, 255)


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


def frames(path):
    """Decode a clip as w x h RGB frames at FPS, cropping it to fill the frame if its shape differs."""
    ff = subprocess.Popen(["ffmpeg", "-v", "error", "-i", path, "-vf",
                           f"scale={w}:{h}:force_original_aspect_ratio=increase,crop={w}:{h},fps={FPS}",
                           "-f", "rawvideo", "-pix_fmt", "rgb24", "-"], stdout=subprocess.PIPE)
    while True:
        data = ff.stdout.read(w * h * 3)
        if len(data) < w * h * 3:
            return
        yield np.frombuffer(data, np.uint8).reshape(h, w, 3)


def honeycomb():
    """For every pixel: how far it sits from its cell's center (0 at the center, 1 on the cell's edge),
    and when its cell starts to open (0 for the middle cell, 1 - GROW for the cells furthest out)."""
    y, x = np.mgrid[0:h, 0:w].astype(np.float32)
    x, y = x - w / 2, y - h / 2                      # a cell sits right on the frame's center
    # pointy-top hex grid: pixel -> axial (q, r), rounded to the nearest cell in cube coordinates
    q, r = (np.sqrt(3) / 3 * x - y / 3) / R, (2 / 3 * y) / R
    s = -q - r
    rq, rr, rs = np.round(q), np.round(r), np.round(s)
    dq, dr, ds = abs(rq - q), abs(rr - r), abs(rs - s)
    rq = np.where((dq > dr) & (dq > ds), -rr - rs, rq)
    rr = np.where((dr >= dq) & (dr > ds), -rq - rs, rr)
    cx, cy = R * np.sqrt(3) * (rq + rr / 2), R * 1.5 * rr
    # distance to the cell's center measured against its three pairs of edges
    ox, oy = np.abs(x - cx), np.abs(y - cy)
    edge = np.maximum(ox, ox / 2 + oy * np.sqrt(3) / 2) / (R * np.sqrt(3) / 2)
    reach = np.hypot(cx, cy)
    return edge, reach / reach.max() * (1 - GROW)


def hexagons(a, b, p):
    """One transition frame at progress p (0-1): each cell shows B inside a hexagon growing from its center."""
    local = np.clip((p - start) / GROW, 0, 1)
    size = local * local * (3 - 2 * local) * 1.05    # smoothstep, a touch past 1 so cells close with no seams
    img = np.where((edge < size)[..., None], b, a)
    img[(edge < size) & (edge >= size - RIM) & (local < 1)] = RIM_COLOR
    return img


A, B, out = sys.argv[1:4]
w, h, length = probe(A)
edge, start = honeycomb()
cut_in = round((length - D) * FPS)   # first frame of the transition
n = round(D * FPS)                   # frames in the transition

enc = subprocess.Popen(["ffmpeg", "-y", "-v", "error", "-f", "rawvideo", "-pix_fmt", "rgb24", "-s", f"{w}x{h}",
                        "-r", str(FPS), "-i", "-", "-i", A, "-i", B,
                        "-filter_complex", f"[1:a][2:a]acrossfade=d={D}[au]", "-map", "0:v", "-map", "[au]",
                        "-c:v", "libx264", "-crf", "18", "-pix_fmt", "yuv420p", "-c:a", "aac", out],
                       stdin=subprocess.PIPE)
clip_a, clip_b = frames(A), frames(B)
for a in islice(clip_a, cut_in):
    enc.stdin.write(a.tobytes())
for i, (a, b) in enumerate(zip(islice(clip_a, n), islice(clip_b, n))):
    enc.stdin.write(hexagons(a, b, i / n).tobytes())
for b in clip_b:
    enc.stdin.write(b.tobytes())
enc.stdin.close()
if enc.wait():
    sys.exit("ffmpeg failed")

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