---
title: "How to add a twirl with Claude (tested prompt and script)"
description: "At 1 second the bottle winds into a purple-and-cream spiral curled around its label while the acrylic block below and the backdrop above stay still, holds for a second, then unwinds back into a bottle by 4.5 seconds. Install one skill and ask Claude Code, or run the Python script we tested yourself."
url: https://www.recharm.com/effects/twirl-swirl
---

# How to add a twirl with Claude

At 1 second the bottle winds into a purple-and-cream spiral curled around its label while the acrylic block below and the backdrop above stay still, holds for a second, then unwinds back into a bottle by 4.5 seconds.

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 a twirl to input.mp4
   Twirl center, as shares across and down the frame: 0.5, 0.52
   Twirl radius as a share of the frame width; nothing outside it moves: 0.45
   ```

## 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 a twirl 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                                                                       |
| -------- | ----------- | ---------------------------------------------------------------------------------- |
| `X, Y`   | `0.5, 0.52` | Twirl center, as shares across and down the frame.                                 |
| `RADIUS` | `0.45`      | Twirl radius as a share of the frame width; nothing outside it moves.              |
| `TWIST`  | `720`       | Degrees the very center turns at full twirl; the turn fades to nothing at the rim. |
| `START`  | `1.0`       | Second the twirl starts winding up.                                                |
| `WIND`   | `1.5`       | Seconds to wind up to full twist.                                                  |
| `HOLD`   | `1.0`       | Seconds held at full twist.                                                        |
| `UNWIND` | `1.0`       | Seconds to unwind back to the plain picture.                                       |

## Without the skill

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

```
Add a twirl effect to input.mp4 with Python, OpenCV (cv2.remap) and numpy, and ffmpeg for reading and writing the video. Twirl a circle of radius 243 px (45% of the 540 px width) centered at x = 270, y = 499 (50% across, 52% down). For each output pixel at distance r and angle theta from that center, sample the input at the same distance and angle theta + A * (1 - r/243)^2 (bilinear), so the center turns by A and the turn fades to nothing at the rim; pixels outside the circle are left exactly as they are. A, in degrees, follows a smoothstep ease (s(p) = p*p*(3-2p), p clamped to 0-1): 0 before 1.0 s, rising to 720 from 1.0 to 2.5 s (A = 720*s((t-1)/1.5)), held at 720 from 2.5 to 3.5 s, falling back to 0 from 3.5 to 4.5 s (A = 720*(1 - s(t-3.5))), and 0 after 4.5 s. Leave the frames untouched whenever A is 0. Keep the size (540x960), frame rate (30fps), 6 second length and the audio, encode H.264 yuv420p, and save it as output.mp4.
```

## Run it without Claude

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

```
python3 twirl-swirl.py input.mp4 output.mp4
```

```
#!/usr/bin/env python3
"""Twirl: a round patch of the picture centered on the bottle winds into a spiral, holds, then unwinds.

Usage: python3 twirl-swirl.py input.mp4 output.mp4
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

X, Y = 0.5, 0.52   # twirl center (0-1 across and down the frame)
RADIUS = 0.45      # twirl radius as a share of the frame width; outside it nothing moves
TWIST = 720        # degrees the very center turns at full twirl; the turn fades to nothing at the rim
START = 1.0        # second the twirl starts winding up
WIND = 1.5         # seconds to wind up to full twist
HOLD = 1.0         # seconds held at full twist
UNWIND = 1.0       # seconds to unwind back to the plain picture

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

def twist_at(t):
    """Degrees the center is turned at second t: wind up, hold, unwind, each eased."""
    if t < START + WIND + HOLD:
        return TWIST * ease((t - START) / WIND)
    return TWIST * (1 - ease((t - START - WIND - HOLD) / UNWIND))

def twirled(img, degrees):
    """Each pixel inside the circle samples the input turned by `degrees` * (1 - distance/radius)^2."""
    phi = theta + math.radians(degrees) * falloff
    return cv2.remap(img, (cx + r * np.cos(phi)).astype(np.float32), (cy + r * np.sin(phi)).astype(np.float32),
                     cv2.INTER_LINEAR, borderMode=cv2.BORDER_REFLECT)

def probe(path):
    info = json.loads(subprocess.check_output(["ffprobe", "-v", "error", "-select_streams", "v:0", "-show_entries",
                                               "stream=width,height,r_frame_rate", "-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)

src, out = sys.argv[1:3]
w, h, rate, fps = probe(src)

# polar coordinates of every pixel around the twirl center, worked out once
cx, cy, radius = X * w, Y * h, RADIUS * w
ys, xs = np.mgrid[0:h, 0:w].astype(np.float32)
r, theta = np.hypot(xs - cx, ys - cy), np.arctan2(ys - cy, xs - cx)
falloff = np.clip(1 - r / radius, 0, 1) ** 2

reader = subprocess.Popen(["ffmpeg", "-v", "error", "-i", src, "-f", "rawvideo", "-pix_fmt", "rgb24", "-"],
                          stdout=subprocess.PIPE)
writer = subprocess.Popen(["ffmpeg", "-y", "-v", "error", "-f", "rawvideo", "-pix_fmt", "rgb24", "-s", f"{w}x{h}",
                           "-r", rate, "-i", "-", "-i", src, "-map", "0:v", "-map", "1:a?", "-c:v", "libx264",
                           "-crf", "18", "-pix_fmt", "yuv420p", "-c:a", "copy", "-shortest", out],
                          stdin=subprocess.PIPE)
i = 0
while True:
    raw = reader.stdout.read(w * h * 3)
    if len(raw) < w * h * 3:
        break
    frame = np.frombuffer(raw, np.uint8).reshape(h, w, 3)
    degrees = twist_at(i / fps)
    if degrees > 0:
        frame = twirled(frame, degrees)
    writer.stdin.write(frame.tobytes())
    i += 1
writer.stdin.close()
if reader.wait() or writer.wait():
    sys.exit("ffmpeg failed")
```

## More distortion and glitch

- [Anaglyph 3D **Anaglyph 3D**](https://www.recharm.com/effects/anaglyph-3d)
- [Black hole **Black hole**](https://www.recharm.com/effects/black-hole)
- [Bulge and pinch **Bulge and pinch**](https://www.recharm.com/effects/bulge-pinch)
- [Datamosh **Datamosh**](https://www.recharm.com/effects/datamosh)
- [Disintegration **Disintegration**](https://www.recharm.com/effects/disintegration)

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