---
title: "How to add a device mockup with Claude (tested prompt and script)"
description: "A black phone with a pill-shaped notch rises from the bottom onto a coral backdrop, the video playing on its screen. Install one skill and ask Claude Code, or run the Python script we tested yourself."
url: https://www.recharm.com/effects/device-mockup
---

# How to add a device mockup with Claude

A black phone with a pill-shaped notch rises from the bottom onto a coral backdrop, the video playing on its screen.

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 device mockup to input.mp4
   Seconds the phone takes to rise up from below the frame: 0.9
   Screen width as a share of the frame width; the screen keeps the video's shape: 0.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.

Add a device mockup 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                                                                    |
| ---------- | ---------------- | ------------------------------------------------------------------------------- |
| `RISE`     | `0.9`            | Seconds the phone takes to rise up from below the frame.                        |
| `SCREEN`   | `0.60`           | Screen width as a share of the frame width; the screen keeps the video's shape. |
| `BEZEL`    | `0.026`          | Bezel thickness as a share of the frame width.                                  |
| `RADIUS`   | `0.07`           | Screen corner radius as a share of the frame width.                             |
| `BODY`     | `(18, 18, 22)`   | Phone body color.                                                               |
| `RIM`      | `(92, 92, 104)`  | Color of the thin edge around the body and of the side buttons.                 |
| `BACKDROP` | `(255, 127, 80)` | Backdrop color.                                                                 |
| `SHADOW`   | `0.40`           | Shadow strength (0-1).                                                          |

## Without the skill

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

```
Turn the 540x960 vertical clip input.mp4 into a phone mockup, 540x960 at 30fps for its full 6 seconds. Fill the frame with a solid coral (255, 127, 80) backdrop. Draw a phone: its screen is 60% of the frame width (324x576 pixels, the clip's 9:16 shape) with corners rounded to 38 pixels, and the clip plays on it scaled down to fit exactly, uncropped. Around the screen a near-black (18, 18, 22) body with a 14-pixel bezel on every side and corners rounded to 52 pixels, edged by a 2-pixel gray-blue (92, 92, 104) rim, with small rounded side buttons in the same gray-blue: two volume buttons on the left edge and a power button on the right. Put a black (0, 0, 0) pill-shaped notch 71x20 pixels centered across the screen, 12 pixels below the screen's top edge. Under the phone add a soft black shadow at 40% opacity, blurred by 19 pixels and shifted 16 pixels down. At rest the phone is centered, so the screen spans x=108 to 432 and y=192 to 768. The phone starts just below the bottom of the frame and rises into place over the first 0.9 seconds with an ease-out (cubic), the clip playing on the screen the whole time, then holds there to the end. Use Python with Pillow to draw the phone (draw anything with Pillow, not ffmpeg's drawtext) and ffmpeg to read and write the video, keep the audio, and save it as output.mp4.
```

## Run it without Claude

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

```
python3 device-mockup.py input.mp4 output.mp4
```

```
#!/usr/bin/env python3
"""Device mockup: a black phone rises onto a coral backdrop with the video playing on its screen.

Usage: python3 device-mockup.py input.mp4 output.mp4
Needs ffmpeg and Pillow (pip install pillow).
"""
import json
import subprocess
import sys

from PIL import Image, ImageDraw, ImageFilter

RISE = 0.9                 # seconds the phone takes to rise up from below the frame
SCREEN = 0.60              # screen width as a share of the frame width (the screen keeps the video's shape)
BEZEL = 0.026              # bezel thickness as a share of the frame width
RADIUS = 0.07              # screen corner radius as a share of the frame width
BODY = (18, 18, 22)        # phone body color (near-black)
RIM = (92, 92, 104)        # thin metal edge around the body and the side buttons
BACKDROP = (255, 127, 80)  # backdrop color (coral)
SHADOW = 0.40              # shadow strength (0-1)

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

def phone_layer():
    """The phone with its screen cut out, plus its shadow, drawn once on a transparent frame-sized layer."""
    layer = Image.new("RGBA", (w, h))
    shadow = Image.new("L", (w, h))
    ImageDraw.Draw(shadow).rounded_rectangle([bx0, by0 + w * 0.03, bx1, by1 + w * 0.03], body_radius,
                                             fill=round(255 * SHADOW))
    layer.paste((0, 0, 0, 255), (0, 0), shadow.filter(ImageFilter.GaussianBlur(w * 0.035)))

    draw = ImageDraw.Draw(layer)
    button = max(3, round(w * 0.008))
    draw.rounded_rectangle([bx0 - button, by0 + ph * 0.17, bx0 + 2, by0 + ph * 0.24], button, fill=RIM)  # volume up
    draw.rounded_rectangle([bx0 - button, by0 + ph * 0.27, bx0 + 2, by0 + ph * 0.34], button, fill=RIM)  # volume down
    draw.rounded_rectangle([bx1 - 2, by0 + ph * 0.22, bx1 + button, by0 + ph * 0.32], button, fill=RIM)  # power
    draw.rounded_rectangle([bx0, by0, bx1, by1], body_radius, fill=RIM)
    draw.rounded_rectangle([bx0 + 2, by0 + 2, bx1 - 2, by1 - 2], body_radius - 2, fill=BODY)

    hole = Image.new("L", (w, h), 255)
    ImageDraw.Draw(hole).rounded_rectangle([sx, sy, sx + sw - 1, sy + sh - 1], screen_radius, fill=0)
    layer.putalpha(Image.composite(layer.getchannel("A"), hole, hole))

    island_w, island_h = sw * 0.22, w * 0.038
    top = sy + w * 0.022
    draw = ImageDraw.Draw(layer)
    draw.rounded_rectangle([w / 2 - island_w / 2, top, w / 2 + island_w / 2, top + island_h], island_h / 2, fill=(0, 0, 0))
    return layer

def mockup_frame(video, t):
    """The frame at time t: the video on the phone's screen, the phone risen by ease_out_cubic(t / RISE)."""
    lift = round((1 - ease_out_cubic(t / RISE)) * (h - by0 + w * 0.08))
    img = Image.new("RGB", (w, h), BACKDROP)
    img.paste(video.resize((sw, sh), Image.LANCZOS), (sx, sy + lift))
    img.paste(phone, (0, lift), phone)
    return img

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)
sw = round(w * SCREEN)
sh = round(sw * h / w)
sx, sy = (w - sw) // 2, (h - sh) // 2
bezel = round(w * BEZEL)
bx0, by0, bx1, by1 = sx - bezel, sy - bezel, sx + sw - 1 + bezel, sy + sh - 1 + bezel
ph = by1 - by0
screen_radius = round(w * RADIUS)
body_radius = screen_radius + bezel
phone = phone_layer()

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 chunk := reader.stdout.read(w * h * 3):
    writer.stdin.write(mockup_frame(Image.frombytes("RGB", (w, h), chunk), i / fps).tobytes())
    i += 1
writer.stdin.close()
if reader.wait() or writer.wait():
    sys.exit("ffmpeg failed")
```

## More product shots

- [Animated feature callouts **Animated feature callouts**](https://www.recharm.com/effects/animated-feature-callouts)
- [Before/after slider **Before/after slider**](https://www.recharm.com/effects/before-after-slider)
- [Best seller badge **Best seller badge**](https://www.recharm.com/effects/best-seller-badge)
- [Callout arrows and circles **Callout arrows and circles**](https://www.recharm.com/effects/callout-arrows-and-circles)
- [Carousel swipe **Carousel swipe**](https://www.recharm.com/effects/carousel-swipe)

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