---
title: "How to add a seamless hand-cover transition with Claude (tested prompt and script)"
description: "A blurry dark hand swipes in from the right, fingers first, blacks out the lens for a moment, then slides off to the left to reveal clip B. Install one skill and ask Claude Code, or run the Python script we tested yourself."
url: https://www.recharm.com/effects/seamless-hand-cover-transition
---

# How to add a seamless hand-cover transition with Claude

A blurry dark hand swipes in from the right, fingers first, blacks out the lens for a moment, then slides off to the left to reveal clip B.

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 seamless hand-cover transition
   Seconds the hand takes to slide over the lens, slowing as it lands: 0.35
   Seconds the lens stays fully covered; the cut to clip B happens halfway through: 0.1
   ```

## 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 seamless hand-cover 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                                                                                  |
| --------- | ------------------------ | --------------------------------------------------------------------------------------------- |
| `IN`      | `0.35`                   | Seconds the hand takes to slide over the lens, slowing as it lands.                           |
| `HOLD`    | `0.1`                    | Seconds the lens stays fully covered; the cut to clip B happens halfway through.              |
| `OUT`     | `0.35`                   | Seconds the hand takes to speed off to the left.                                              |
| `DRIFT`   | `0.08`                   | How far the hand keeps creeping left while it covers the lens, as a share of the frame width. |
| `FINGERS` | `(0.5, 0.6, 0.56, 0.42)` | How far each finger reaches past the palm, as a share of the frame width, top to bottom.      |
| `SOFT`    | `0.03`                   | Blur on the hand's edge as a share of the frame width; more looks closer to the lens.         |
| `SKIN`    | `(24, 16, 14)`           | Color of the hand in its own shadow, which fills the screen at the cut.                       |
| `RIM`     | `(150, 98, 76)`          | Warmer color along the hand's edges, where light catches the skin.                            |

## 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 hand-cover transition that starts 0.8 seconds before clipA.mp4 ends (11.2 seconds in all for two 6 second clips), as if a hand passed right in front of the lens. Build the transition frames with Python, numpy and Pillow; W and H are the frame width and height (540x960), and X is the left edge of the hand drawing. Draw the hand into a mask with Pillow: a palm, a rounded rectangle from X+0.75W to X+2.0W across and from -0.2H to 1.2H down with a corner radius of 0.3W; four fingers pointing left, rounded rectangles 0.17H tall with fully rounded ends, top edges at 0.10H, 0.27H, 0.44H and 0.61H, each running from X+0.95W left to X+0.75W minus 0.50W, 0.60W, 0.56W and 0.42W (top to bottom); and a thumb, a rounded rectangle from X+0.35W to X+1.05W across and 0.78H to 0.984H down (corner radius 0.102H), rotated 25 degrees counterclockwise about the point (X+1.05W, 0.78H). Blur the mask with a Gaussian blur of radius 0.03W so the hand looks out of focus. Color the hand from a second copy of the mask blurred by 0.06W: with k = clip((blurred/255 - 0.5) * 2, 0, 1), the color is (150, 98, 76) * (1 - k) + (24, 16, 14) * k, so its edges look like warm skin and its middle is near black. Paste it over the frame with the soft mask. Move it with t in seconds from the start of the transition and cover = -0.835W: for t < 0.35, X = W + (cover - W) * (1 - (1 - p)^2) with p = t / 0.35 (the hand slows as it lands); from 0.35 to 0.45, X creeps linearly from cover to cover - 0.08W while the lens is fully covered; from 0.45 to 0.8, X = cover - 0.08W + (-2.1W - cover + 0.08W) * p^2 with p = (t - 0.45) / 0.35 (it speeds off to the left). Show clip A under the hand up to 0.4 seconds into the transition and clip B after that, with B playing from its first frame at the start of the transition. Don't use ffmpeg's drawtext. Use ffmpeg for the rest: match B's size and frame rate to A (30fps), crossfade the audio over the same 0.8 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 `seamless-hand-cover-transition.py` and run:

```
python3 seamless-hand-cover-transition.py clipA.mp4 clipB.mp4 output.mp4
```

```
#!/usr/bin/env python3
"""Hand cover: an out-of-focus hand swipes in from the right over clip A, blacks out the lens, then swipes off to the left onto clip B.

Usage: python3 seamless-hand-cover-transition.py clipA.mp4 clipB.mp4 output.mp4  (both clips need an audio track)
Needs ffmpeg, Pillow and numpy (pip install pillow numpy). Filmed hand covers look best; this fakes one for any two clips.
"""
import json
import subprocess
import sys

import numpy as np
from PIL import Image, ImageDraw, ImageFilter

IN = 0.35                # seconds the hand takes to slide over the lens (slowing as it lands)
HOLD = 0.1               # seconds the lens stays covered; the cut to B happens halfway through
OUT = 0.35               # seconds the hand takes to slide off to the left (speeding up)
DRIFT = 0.08             # how far the palm keeps creeping left while it covers the lens, as a share of the width
FINGERS = (0.5, 0.6, 0.56, 0.42)  # how far each finger reaches past the palm, as a share of the width, top to bottom
SOFT = 0.03              # blur on the hand's edge as a share of the width (it's too close to the lens to be sharp)
SKIN = (24, 16, 14)      # color of the hand in its own shadow
RIM = (150, 98, 76)      # warmer color where light catches the hand's edges
FPS = 30

a_path, b_path, out_path = sys.argv[1:4]
info = json.loads(subprocess.run(
    ["ffprobe", "-v", "error", "-select_streams", "v:0", "-show_entries", "stream=width,height:format=duration",
     "-of", "json", a_path], capture_output=True, check=True).stdout)
w, h = info["streams"][0]["width"], info["streams"][0]["height"]
D = IN + HOLD + OUT
offset = float(info["format"]["duration"]) - D
n = round(D * FPS)

def hand():
    """The hand as an RGB image and a soft-edged mask: a palm 1.25 frames wide and taller than the frame, with four
    fingers pointing left (the way it moves) and a thumb below them. Returns them with the palm's center (x)."""
    hw, hh = round(w * 2.1), round(h * 1.4)
    palm_left, palm_right = round(w * 0.75), round(w * 2.0)
    mask = Image.new("L", (hw, hh))
    draw = ImageDraw.Draw(mask)
    draw.rounded_rectangle([palm_left, 0, palm_right, hh], radius=round(w * 0.3), fill=255)
    finger = round(h * 0.17)
    for i, length in enumerate(FINGERS):   # index finger at the top, little finger at the bottom
        y = round(h * 0.3) + i * finger
        draw.rounded_rectangle([palm_left - w * length, y, palm_left + w * 0.2, y + finger - 2], radius=finger // 2, fill=255)
    thumb = Image.new("L", (hw, hh))
    y = round(h * 0.3) + 4 * finger
    ImageDraw.Draw(thumb).rounded_rectangle([palm_left - w * 0.4, y, palm_left + w * 0.3, y + finger * 1.2],
                                            radius=round(finger * 0.6), fill=255)
    mask.paste(255, mask=thumb.rotate(25, center=(palm_left + w * 0.3, y), resample=Image.BICUBIC))
    alpha = mask.filter(ImageFilter.GaussianBlur(w * SOFT))
    # deep inside the hand is near black; toward its edges it warms up to RIM, like skin catching the light
    inside = np.asarray(mask.filter(ImageFilter.GaussianBlur(w * 0.06)), np.float32)[:, :, None] / 255
    inside = np.clip((inside - 0.5) * 2, 0, 1)
    rgb = np.array(RIM, np.float32) * (1 - inside) + np.array(SKIN, np.float32) * inside
    return Image.fromarray(rgb.astype(np.uint8)), alpha, (palm_left + palm_right) / 2

def hand_x(t):
    """Left edge of the hand image: from just off the right edge, to the palm centered over the lens, to off the left edge."""
    cover = w / 2 - palm_mid + w * DRIFT / 2
    if t < IN:
        p = t / IN
        return w + (cover - w) * (1 - (1 - p) ** 2)
    if t < IN + HOLD:
        return cover - w * DRIFT * (t - IN) / HOLD
    p = (t - IN - HOLD) / OUT
    gone = -skin.width   # far enough left that the hand has left the frame
    return cover - w * DRIFT + (gone - cover + w * DRIFT) * p ** 2

def frames(path, start):
    """The n frames from `start`, fitted to A's size, as RGB arrays."""
    raw = subprocess.run(
        ["ffmpeg", "-v", "error", "-ss", str(start), "-i", path, "-frames:v", str(n),
         "-vf", f"scale={w}:{h}:force_original_aspect_ratio=increase,crop={w}:{h},fps={FPS}",
         "-f", "rawvideo", "-pix_fmt", "rgb24", "-"], capture_output=True, check=True).stdout
    return np.frombuffer(raw, np.uint8).reshape(-1, h, w, 3)

def cover_frame(picture, t):
    img = Image.fromarray(picture)
    img.paste(skin, (round(hand_x(t)), round(-h * 0.2)), alpha)
    return img.tobytes()

skin, alpha, palm_mid = hand()
tail, head = frames(a_path, offset), frames(b_path, 0)
cut = IN + HOLD / 2
middle = b"".join(cover_frame(tail[i] if i / FPS < cut else head[i], i / FPS) for i in range(n))

# A up to the hand, then the hand frames from stdin, then the rest of B; the sound crossfades underneath
subprocess.run(
    ["ffmpeg", "-y", "-v", "error", "-i", a_path, "-i", b_path,
     "-f", "rawvideo", "-pix_fmt", "rgb24", "-s", f"{w}x{h}", "-r", str(FPS), "-i", "-",
     "-filter_complex",
     f"[0:v]fps={FPS},trim=end_frame={round(offset * FPS)},setpts=PTS-STARTPTS,format=yuv420p,setsar=1[a];"
     f"[2:v]format=yuv420p,setsar=1[m];"
     f"[1:v]scale={w}:{h}:force_original_aspect_ratio=increase,crop={w}:{h},fps={FPS},"
     f"trim=start_frame={n},setpts=PTS-STARTPTS,format=yuv420p,setsar=1[b];"
     f"[a][m][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_path],
    input=middle, check=True)
```

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