---
title: "How to add a ripple transition with Claude (tested prompt and script)"
description: "Rings of water ripples spread out from the middle of clip A, bending the picture with light and dark bands, and clip B shows inside the widening wave until it fills the frame. Install one skill and ask Claude Code, or run the Python script we tested yourself."
url: https://www.recharm.com/effects/ripple-transition
---

# How to add a ripple transition with Claude

Rings of water ripples spread out from the middle of clip A, bending the picture with light and dark bands, and clip B shows inside the widening wave until it fills the frame.

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 ripple transition
   Length of the transition in seconds: 1.2
   Where the ripples start, 0-1 across and down the frame: 0.5, 0.5
   ```

## 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 ripple 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`          | `1.2`      | Length of the transition in seconds.                               |
| `X, Y`       | `0.5, 0.5` | Where the ripples start, 0-1 across and down the frame.            |
| `WAVELENGTH` | `70`       | Distance between ripple rings in pixels.                           |
| `AMP`        | `14`       | Furthest a ripple bends the picture, in pixels.                    |
| `AHEAD`      | `60`       | How far the ripples reach ahead of the wave front, in pixels.      |
| `BEHIND`     | `200`      | How far the trailing rings reach behind the wave front, in pixels. |
| `EDGE`       | `60`       | Width of the soft edge where clip A turns into clip B, in pixels.  |
| `SHINE`      | `28`       | Brightness of the light and dark bands on the ripples.             |
| `FPS`        | `30`       | Output frame rate.                                                 |

## 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 1.2 second ripple transition that starts 1.2 seconds before clipA.mp4 ends, so the output is A's length plus B's length minus 1.2 seconds (10.8 seconds for two 6 second clips; B starts from its first frame when the transition starts). Use Python with numpy and OpenCV (cv2.remap), working frame by frame at 30fps and fitting B to A's size first. On each transition frame, with progress p from 1/36 to 1: a wave front grows from the center of the frame at radius R = p * (distance from the center to a corner + 60) pixels. For every pixel at distance r from the center, let d = r - R, strength = sin(pi * p) * exp(-(d / s)^2) with s = 60 pixels when d > 0 (ahead of the front) and 200 pixels behind it, and phase = 2 * pi * d / 70. Bend both clips radially by sampling each pixel from 14 * strength * sin(phase) pixels closer to the center (reflect at the borders). Show B inside the front and A outside it, mixing with weight clip(0.5 - d / 60, 0, 1) for B, then add 28 * strength * cos(phase) to all three channels so the rings get light and dark bands like water. Leave every frame outside the transition untouched, so clip A is clean until 4.8 seconds and clip B is clean from 6.0 seconds. Crossfade the audio over the same 1.2 seconds. Keep the audio and save it as output.mp4 (H.264, yuv420p).
```

## Run it without Claude

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

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

```
#!/usr/bin/env python3
"""Ripple transition: rings of water ripples spread out from the middle of clip A, bending the picture, and clip B shows inside the widening wave.

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

import cv2
import numpy as np

D = 1.2            # transition length in seconds
X, Y = 0.5, 0.5    # where the ripples start (0-1 across and down the frame)
WAVELENGTH = 70    # distance between ripple rings in pixels
AMP = 14           # furthest a ripple bends the picture, in pixels
AHEAD = 60         # how far the ripples reach ahead of the wave front, in pixels
BEHIND = 200       # how far the trailing rings reach behind it, in pixels
EDGE = 60          # width of the soft edge where A turns into B, in pixels
SHINE = 28         # brightness of the light and dark bands on the ripples
FPS = 30

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):
    """Every frame of a clip, fitted to A's size at 30fps, as an RGB array."""
    fit = f"scale={w}:{h}:force_original_aspect_ratio=increase,crop={w}:{h},fps={FPS}"
    ff = subprocess.Popen(["ffmpeg", "-v", "error", "-i", path, "-vf", fit, "-f", "rawvideo", "-pix_fmt", "rgb24", "-"],
                          stdout=subprocess.PIPE)
    while True:
        chunk = ff.stdout.read(w * h * 3)
        if len(chunk) < w * h * 3:
            return
        yield np.frombuffer(chunk, np.uint8).reshape(h, w, 3)

def ripple(a, b, p):
    """Frame at progress p (0-1): a ring-shaped wave grows from (X, Y), B inside it and A outside, both bent by the ripples."""
    d = r - p * reach                                                    # distance past the wave front
    strength = np.sin(np.pi * p) * np.exp(-(d / np.where(d > 0, AHEAD, BEHIND)) ** 2)
    phase = 2 * np.pi * d / WAVELENGTH
    push = AMP * strength * np.sin(phase)                                # outward (+) or inward (-) bend
    map_x = (xs - push * ux).astype(np.float32)
    map_y = (ys - push * uy).astype(np.float32)
    bent_a = cv2.remap(a, map_x, map_y, cv2.INTER_LINEAR, borderMode=cv2.BORDER_REFLECT)
    bent_b = cv2.remap(b, map_x, map_y, cv2.INTER_LINEAR, borderMode=cv2.BORDER_REFLECT)
    inside = np.clip(0.5 - d / EDGE, 0, 1)[..., None]
    out = bent_a * (1 - inside) + bent_b * inside
    return out + (SHINE * strength * np.cos(phase))[..., None]           # light on one slope of each ring, shade on the other

a_path, b_path, out = sys.argv[1:4]
w, h, a_len = probe(a_path)
ys, xs = np.mgrid[0:h, 0:w].astype(np.float32)
dx, dy = xs - X * w, ys - Y * h
r = np.hypot(dx, dy)
ux, uy = dx / np.maximum(r, 1), dy / np.maximum(r, 1)
reach = np.hypot(max(X, 1 - X) * w, max(Y, 1 - Y) * h) + EDGE           # the front passes the farthest corner at the end
start = round((a_len - D) * FPS)    # first frame of the transition
steps = round(D * FPS)
ff = subprocess.Popen(["ffmpeg", "-y", "-v", "error", "-f", "rawvideo", "-pix_fmt", "rgb24", "-s", f"{w}x{h}",
                       "-r", str(FPS), "-i", "-", "-i", a_path, "-i", b_path,
                       "-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)
b_frames = frames(b_path)
for i, a in enumerate(frames(a_path)):
    if i >= start:
        a = ripple(a, next(b_frames), (i - start + 1) / steps)
    ff.stdin.write(np.clip(a, 0, 255).astype(np.uint8).tobytes())
for b in b_frames:
    ff.stdin.write(b.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)
