---
title: "How to add a cube transition with Claude (tested prompt and script)"
description: "Clip A and clip B sit on two sides of a 3D box that turns a quarter turn to the left, A swinging away into the dark as B's side swings round to face you. Install one skill and ask Claude Code, or run the Python script we tested yourself."
url: https://www.recharm.com/effects/cube-transition
---

# How to add a cube transition with Claude

Clip A and clip B sit on two sides of a 3D box that turns a quarter turn to the left, A swinging away into the dark as B's side swings round to face you.

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

## 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

   ```
   Join clipA.mp4 and clipB.mp4 with a cube transition
   Length of the transition in seconds: 0.8
   Camera distance from the cube's center in cube widths; smaller means stronger perspective: 2.0
   ```

## 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 cube transition.

## 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                                                                               |
| ------------ | -------------- | ------------------------------------------------------------------------------------------ |
| `D`          | `0.8`          | Length of the transition in seconds.                                                       |
| `CAMERA`     | `2.0`          | Camera distance from the cube's center in cube widths; smaller means stronger perspective. |
| `PULL`       | `0.15`         | How far the cube pulls back mid-turn, as a share of its size.                              |
| `SHADE`      | `0.45`         | How much a side darkens as it turns away from the camera.                                  |
| `BACKGROUND` | `(14, 14, 18)` | Color behind the cube.                                                                     |

## Without the skill

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

```
Join clipA.mp4 and clipB.mp4 with a 0.8 second 3D cube transition, overlapping the clips by 0.8 seconds (the output is 11.2 seconds). Over the last 0.8 seconds of A, put A's frames on the front side of a box as wide and deep as the frame (540 pixels) and as tall as it (960), and the first 0.8 seconds of B on its right side, then turn the box 90 degrees about its vertical axis so the right side comes to the front, with a cubic ease-in-out (slow start, fast middle, slow landing). Project it in perspective from a camera 2 frame widths from the box's center, with the focal length set so the front side exactly fills the frame before the turn, and shrink the projection about the frame center by 15% at the middle of the turn (scale = 1 - 0.15 * sin(pi * eased progress)). Darken each side as it turns away (brightness 1 - 0.45 * (1 - cos) of the angle between it and the camera) and fill the space around the box with dark gray (14, 14, 18). Warp the sides with OpenCV's warpPerspective using Python 3, OpenCV and numpy, and use ffmpeg for the rest. Before the transition the output is A untouched, after it B carries on from 0.8 seconds in. Crossfade the audio over the same 0.8 seconds. Match B's size and frame rate to A and save it as output.mp4 (H.264, yuv420p, 30fps).
```

## Run it without Claude

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

```
python3 cube-transition.py clipA.mp4 clipB.mp4 output.mp4
```

```
#!/usr/bin/env python3
"""Cube transition: clip A and clip B sit on two sides of a 3D cube that turns left to bring B to the front.

Usage: python3 cube-transition.py clipA.mp4 clipB.mp4 output.mp4  (both clips need an audio track)
Needs ffmpeg, OpenCV and numpy (pip install opencv-python-headless numpy).
"""
import json
import math
import subprocess
import sys

import cv2
import numpy as np

D = 0.8                     # transition length in seconds
CAMERA = 2.0                # camera distance from the cube's center, in cube widths (smaller = stronger perspective)
PULL = 0.15                 # how far the cube pulls back mid-turn, as a share of its size
SHADE = 0.45                # how much a side darkens as it turns away from the camera
BACKGROUND = (14, 14, 18)   # color behind the cube

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 frames(path, start):
    """The transition's frames from path, starting at `start` seconds, fitted to A's size and frame rate."""
    raw = subprocess.check_output(["ffmpeg", "-v", "error", "-ss", f"{start:.3f}", "-i", path, "-frames:v", str(n),
                                   "-vf", f"scale={w}:{h}:force_original_aspect_ratio=increase,crop={w}:{h},fps={rate}",
                                   "-f", "rawvideo", "-pix_fmt", "rgb24", "-"])
    return np.frombuffer(raw, np.uint8)[:n * h * w * 3].reshape(n, h, w, 3)

def ease(p):
    """Slow start, fast middle, slow landing (cubic ease-in-out)."""
    return 4 * p ** 3 if p < 0.5 else 1 - (-2 * p + 2) ** 3 / 2

def project(points, angle, scale):
    """Screen positions of 3D points on a cube of width w centered at the origin, turned `angle` radians about
    its vertical axis, seen by a camera that shows the front side exactly filling the frame when angle is 0."""
    cam = CAMERA * w
    focal = (cam - w / 2) * scale
    out = []
    for x, y, z in points:
        x, z = x * math.cos(angle) - z * math.sin(angle), x * math.sin(angle) + z * math.cos(angle)
        out.append((w / 2 + focal * x / (cam - z), h / 2 + focal * y / (cam - z)))
    return np.float32(out)

def paint(canvas, img, corners, light):
    """Warp img onto the four screen corners (top-left, top-right, bottom-right, bottom-left), shaded by light."""
    src = np.float32([[0, 0], [w, 0], [w, h], [0, h]])
    m = cv2.getPerspectiveTransform(src, corners)
    face = cv2.warpPerspective(img, m, (w, h), flags=cv2.INTER_LINEAR).astype(np.float32) * light
    mask = cv2.warpPerspective(np.ones((h, w), np.float32), m, (w, h), flags=cv2.INTER_LINEAR)[..., None]
    canvas[:] = canvas * (1 - mask) + face * mask

def cube_frame(i):
    """Frame i of the transition: A on the cube's front, B on its right side, turned a quarter turn in all."""
    p = ease(i / (n - 1))
    angle = p * math.pi / 2
    scale = 1 - PULL * math.sin(p * math.pi)
    x, y, z = w / 2, h / 2, w / 2
    front = project([(-x, -y, z), (x, -y, z), (x, y, z), (-x, y, z)], angle, scale)
    right = project([(x, -y, z), (x, -y, -z), (x, y, -z), (x, y, z)], angle, scale)
    canvas = np.empty((h, w, 3), np.float32)
    canvas[:] = BACKGROUND
    if math.cos(angle) > 1e-3:
        paint(canvas, a_frames[i], front, 1 - SHADE * (1 - math.cos(angle)))
    if math.sin(angle) > 1e-3:
        paint(canvas, b_frames[i], right, 1 - SHADE * (1 - math.sin(angle)))
    return canvas.clip(0, 255).astype(np.uint8)

a_path, b_path, out = sys.argv[1:4]
w, h, rate, fps, a_len = probe(a_path)
n = round(D * fps)
offset = a_len - D
a_frames, b_frames = frames(a_path, offset), frames(b_path, 0)

# A up to the transition, the cube frames, then B from where the transition ends; the sound crossfades over D
ff = subprocess.Popen(["ffmpeg", "-y", "-v", "error", "-i", a_path, "-i", b_path,
                       "-f", "rawvideo", "-pix_fmt", "rgb24", "-s", f"{w}x{h}", "-r", rate, "-i", "-",
                       "-filter_complex",
                       f"[0:v]trim=end={offset:.4f},setpts=PTS-STARTPTS,fps={rate},format=yuv420p,setsar=1[a];"
                       f"[1:v]scale={w}:{h}:force_original_aspect_ratio=increase,crop={w}:{h},fps={rate},"
                       f"trim=start={D},setpts=PTS-STARTPTS,format=yuv420p,setsar=1[b];"
                       f"[2:v]format=yuv420p,setsar=1[c];"
                       f"[a][c][b]concat=n=3:v=1:a=0[v];[0:a][1:a]acrossfade=d={D}[au]",
                       "-map", "[v]", "-map", "[au]", "-c:v", "libx264", "-crf", "18", "-pix_fmt", "yuv420p",
                       "-c:a", "aac", out], stdin=subprocess.PIPE)
for i in range(n):
    ff.stdin.write(cube_frame(i).tobytes())
ff.stdin.close()
if ff.wait():
    sys.exit("ffmpeg failed")
```

## More transitions

- [Barn door transition **Barn door transition**](https://www.recharm.com/effects/barn-door)
- [Blinds transition **Blinds transition**](https://www.recharm.com/effects/blinds-transition)
- [Block dissolve **Block dissolve**](https://www.recharm.com/effects/block-dissolve)
- [Blur transition **Blur transition**](https://www.recharm.com/effects/blur-transition)
- [Bounce transition **Bounce transition**](https://www.recharm.com/effects/bounce-transition)

## 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)
